Variables, Data Types, and f-Strings

8 min read

Every variable in Python has a type — but unlike Java, you don't declare it. You assign a value, and Python figures out the type from what you assigned. This makes Python feel close to JavaScript: count = 5 is a complete declaration. The trade-off is that the same variable can hold a string today and a number tomorrow — Python won't stop you. The discipline of picking a type and keeping it consistent is yours, not the compiler's. This lesson walks through how to declare variables, what types Python ships with, how to convert between them, and the naming conventions every Python codebase you'll meet expects you to follow.

Declaring a variable — there is no declaration

The shape is always name = value:

test_name = "Login Test"      # str
status_code = 200             # int
response_time = 1.25          # float
is_passed = True              # bool
middle_name = None            # NoneType

No keywords. No let, const, var. No String, int, boolean. Just a name and a value, separated by =.

You can reassign at any time, and the type can change:

result = "pending"
result = 42
result = True

Python tolerates this; idiomatic Python avoids it. Reusing a name for different types is a fast way to confuse the next person who reads the code (often you, in three months).

Checking a value's type

Use type():

print(type(200))          # <class 'int'>
print(type(1.25))         # <class 'float'>
print(type("hi"))         # <class 'str'>
print(type(True))         # <class 'bool'>
print(type(None))         # <class 'NoneType'>

type() is occasionally useful for debugging. For checking whether a value is of a specific type, the idiomatic form is isinstance(x, int) — but you'll rarely need this in QA code; type hints (covered below) carry more weight.

The Python data types you'll meet daily

str — strings. Single or double quotes both work; pick one and be consistent inside a project. Triple quotes for multi-line:

single = 'staging'
double = "staging"
multi = """line one
line two
line three"""

Unlike Java's char vs String distinction, Python has only str. A single character is just a str of length 1. There are no character literals.

int — integers. No size limit (unlike Java's 32-bit int). Python happily handles huge numbers:

small = 200
big = 999_999_999_999_999_999_999    # underscores for readability — they're ignored
neg = -1

The _ separators are visual only; 1_000_000 and 1000000 are the same int.

float — decimals (64-bit floating point under the hood):

response_time = 1.25
pi = 3.14159
sci = 1.5e3      # 1500.0 in scientific notation

float follows the usual IEEE-754 quirks — 0.1 + 0.2 is 0.30000000000000004. For test code that's fine; for money use the decimal module.

boolTrue or False. Capital first letters. This trips up everyone coming from JavaScript or Java:

is_passed = True
is_failed = False
# is_passed = true   # NameError: name 'true' is not defined

bool is technically a subtype of int (True == 1, False == 0), but you don't need to think about that.

None — Python's null. Represents "no value here":

middle_name = None
api_response = None     # we'll fetch one shortly

Use is (not ==) to compare with None:

if api_response is None:
    print("no response yet")

x is None is the canonical idiom — both faster and more correct than x == None.

list — ordered, mutable collection. Like an array in JS, an ArrayList in Java:

browsers = ["chromium", "firefox", "webkit"]
status_codes = [200, 201, 204]

We'll cover lists in depth in chapter 3.

dict — key-value pairs. Like an object in JS or a HashMap in Java:

config = {"env": "staging", "retries": 3, "timeout": 30}
print(config["env"])    # "staging"

Also covered properly in chapter 3.

Type conversion

Python doesn't auto-convert across types — you ask explicitly. The conversion functions are named after the target type:

int("200")        # 200      — str → int
str(200)          # "200"    — int → str
float("1.25")     # 1.25     — str → float
int(1.25)         # 1        — float → int (truncates, doesn't round)

A quirky but useful conversion is bool(x). Python treats certain values as falsy0, empty string "", empty list [], empty dict {}, None. Everything else is truthy:

bool(0)           # False
bool(1)           # True
bool(-1)          # True   — non-zero is truthy
bool("")          # False
bool("hello")     # True
bool([])          # False  — empty list
bool([0])         # True   — non-empty, even though it contains 0
bool(None)        # False

This is why you can write if response_time: instead of if response_time is not None and response_time > 0: — Python's truthiness rules collapse common checks into a single condition. Use carefully: if count: succeeds for any non-zero number, which usually isn't what you want when zero is a valid value.

Reading values that come in as strings

Anything from a CSV, an environment variable, or input() arrives as a string. Convert before doing math:

import os
 
raw_timeout = os.environ.get("TIMEOUT", "30")     # always a str
timeout = int(raw_timeout)                         # now an int
print(timeout * 2)                                 # 60

Skipping the conversion gives confusing results: "30" * 2 is the string "3030", not the int 60. JavaScript would coerce the string to a number with *; Python keeps the string and repeats it.

Naming conventions — snake_case rules everything

Python's style guide (PEP 8) is about as universal as conventions get:

  • snake_case for variables and functions: test_case_name, max_retries, find_element_by_id.
  • PascalCase for class names: LoginPage, TestRunner, ApiClient.
  • UPPER_SNAKE_CASE for module-level constants: BASE_URL, MAX_TIMEOUT, DEFAULT_RETRIES.
  • _leading_underscore signals "intended as internal" — Python doesn't enforce private, this is a hint.
  • __dunder__ (two underscores either side) is reserved for Python's special methods like __init__ — never invent your own dunder names.
BASE_URL = "https://staging.myapp.com"   # constant
MAX_RETRIES = 3                           # constant
 
def calculate_pass_rate(passed, total):   # function
    return passed / total
 
class LoginPage:                          # class
    pass

Java engineers will find this jarring at first — Java uses camelCase for variables. Don't mix conventions; the moment you write testCaseName in Python a reviewer will flag it.

Constants — convention only

Python has no const or final keyword. The all-caps name is purely a signal to humans:

MAX_RETRIES = 3
MAX_RETRIES = 99      # Python allows this — nothing stops you

A linter may warn you, but the language won't. The discipline is yours: all-caps means "treat as immutable." If you need enforced constants, the enum module helps; for most QA scripts, the convention is enough.

Type hints — optional documentation

Python 3.5+ supports type hints — annotations that document the expected type without enforcing it at runtime. Familiar to anyone coming from TypeScript:

test_name: str = "Login Test"
status_code: int = 200
is_passed: bool = True
 
def calculate_pass_rate(passed: int, total: int) -> float:
    return passed / total

Type hints are:

  • Not enforced at runtime. test_name: str = 42 runs without complaint. The hint is documentation.
  • Read by IDEs. VS Code and PyCharm use them for autocompletion and "wrong type" warnings as you type.
  • Read by mypy. A separate tool that checks hints across a whole codebase. Common in serious Python QA projects.

In this course we'll use hints sparingly at first — Python's idiom is "infer when obvious, hint when helpful." Heavy hint usage is more common in large team codebases.

A QA configuration block — every type pulling its weight

BASE_URL: str = "https://staging.myapp.com"
MAX_RETRIES: int = 3
SLA_THRESHOLD: float = 2.0
IS_PRODUCTION: bool = False
 
test_env = "staging"
current_retry = 0
last_response_time = 1.85
is_passing = last_response_time < SLA_THRESHOLD
last_error = None
 
print(f"Env:           {test_env}")
print(f"Base URL:      {BASE_URL}")
print(f"Max retries:   {MAX_RETRIES}")
print(f"Last response: {last_response_time}s")
print(f"SLA met?       {is_passing}")
print(f"Last error:    {last_error}")

Output:

Env:           staging
Base URL:      https://staging.myapp.com
Max retries:   3
Last response: 1.85s
SLA met?       True
Last error:    None

Notice how None and True print directly — Python converts them to text representations. Compare to Java where you'd need String.valueOf to do the same.

Python types vs JavaScript and Java

The takeaway: Python's everyday types (str, int, float, bool, None, list, dict) cover what you'll need 95% of the time. You don't pick between int and long or double and float — Python only has one of each.

⚠️ Common mistakes

  • Lowercase true / false / none. Python's booleans are True and False; "nothing" is None. Lowercase versions are the JavaScript/Java spelling and Python flags them as NameError: name 'true' is not defined.
  • Comparing to None with ==. Use is, not ==. x is None is the idiom; x == None works most of the time but breaks for objects that override __eq__. is checks identity (same object), which is what you actually want.
  • Multiplying a string by a number expecting concatenation. "5" * 3 is "555", not 15. Python's * operator on a string repeats it. Convert with int("5") * 3 if you wanted arithmetic.

🎯 Practice task

Build a typed configuration block. 20-30 minutes.

  1. Create a file load_test_config.py.
  2. Declare these variables with sensible names:
    • a str for the API base URL
    • a str for the environment name (e.g. "staging")
    • an int for max concurrent users
    • a float for the p95 latency SLA in milliseconds (e.g. 250.0)
    • a bool for whether dry-run mode is on
    • a constant MAX_RETRIES set to 3 (use UPPER_SNAKE_CASE)
  3. Print each value with an f-string, e.g. print(f"Env: {env}").
  4. Read a value as a string — max_users_input = "150" — convert to int with int(...). Print both the original string and the parsed int. Use type() to confirm the second one is now an int.
  5. Read a decimal as a string — sla_input = "2.5" — convert with float(...). Then convert to int with int(...). Notice the cast truncates 2.5 to 2, just like in Java.
  6. Run with python load_test_config.py. Confirm the output matches your declarations.
  7. Stretch: add type hints to every variable. e.g. BASE_URL: str = "...", max_users: int = 150. The script behaves identically — hints don't change runtime behaviour. If you have mypy installed (pip install mypy), run mypy load_test_config.py and read what it reports.

You can now declare and convert every type you'll need for the next several chapters. The next lesson kicks off chapter 2 with control flow — if, elif, else — and the operators you use to build them.

// tip to track lessons you complete and pick up where you left off across devices.