4.8 Variables
Declaring variables without a separate declaration step, the assignment operator, multiple/chained assignment, and dynamic typing in Python.
Declaring Variables
What Is It?
Unlike many languages, Python has no separate declaration step — a variable comes into existence the moment you assign a value to it.
Why Is It Used?
Less boilerplate, faster to write.
How Is It Used?
Just write name = value.
>>> username = "deploy_bot"
>>> username
'deploy_bot'
Python variables are names, not fixed storage boxes with permanent types. The name username refers to a string object, and a later assignment can make it refer to a different object.
Assignment
The = operator binds a name to a value (technically, to an object in memory). Re-assigning simply points the name at a new object; it doesn’t modify the old one.
>>> retries = 3
>>> retries = retries + 1
>>> retries
4
Names and Objects
Assignment creates or updates a binding between a name and an object. It does not copy the object automatically.
The old integer object is not changed. The name is simply rebound to the result of retries + 1.
Use == to compare values and is only to compare object identity. In normal application code, is is most commonly used with the singleton None:
value = None
if value is None:
print("No value was supplied")
Multiple Assignment
What Is It?
Assigning several variables in one statement, either to different values or the same one.
Why Is It Used?
It reduces repetition when initializing related variables together.
>>> a, b, c = 1, 2, 3 # unpack three values at once
>>> a, b, c
(1, 2, 3)
>>> x = y = z = 0 # all three names point at the same value
>>> x, y, z
(0, 0, 0)
Python also supports starred unpacking when the number of remaining values is variable:
>>> first, *middle, last = [10, 20, 30, 40]
>>> first, middle, last
(10, [20, 30], 40)
The number of values must still be compatible with the assignment pattern. Otherwise Python raises ValueError.
Mutable Objects and Aliasing
Multiple names can refer to the same mutable object. Mutating the object is visible through every alias, while rebinding one name is not:
>>> primary = []
>>> secondary = primary
>>> secondary.append("api")
>>> primary
['api']
>>> secondary = ["worker"]
>>> primary
['api']
This is why a mutable default argument is dangerous:
def add_service(service_name, services=[]):
services.append(service_name)
return services
The same list is reused between calls. Use None as a sentinel and create the list inside the function instead:
def add_service(service_name, services=None):
if services is None:
services = []
services.append(service_name)
return services
Type Annotations
Annotations document the expected type without changing Python’s dynamic runtime behavior. Python does not enforce the annotation by itself:
retries: int = 3
service_name: str = "api"
retries = "three" # Allowed at runtime, but a type checker should report it.
Use annotations for function boundaries and configuration data, then use a type checker such as Pyright or mypy in CI when the project requires static checks.
Dynamic Typing
Covered in depth in Introduction to Python, Section 1.3 — a variable’s type is simply whatever its current value’s type is, and can change on reassignment:
>>> v = 10
>>> type(v)
<class 'int'>
>>> v = "text"
>>> type(v)
<class 'str'>
Dynamic typing does not mean a value has no type. Every object has a type; it means the name is not permanently restricted to one type by the language.
Variables in DevOps Scripts
Keep external configuration at the boundary of a script, validate it, and pass explicit values to the functions that need them:
import os
def get_deployment_config() -> tuple[str, str]:
cluster_name = os.environ.get("EKS_CLUSTER_NAME")
region = os.environ.get("AWS_REGION", "us-east-1")
if not cluster_name:
raise RuntimeError("EKS_CLUSTER_NAME is required")
return cluster_name, region
cluster_name, region = get_deployment_config()
print(f"Deploying {cluster_name} in {region}")
This keeps secrets and environment-specific values outside the source code while making the function’s inputs clear and testable. Do not print secret environment variables in CI logs.
Variable Scope
A name created inside a function is local to that function unless the code explicitly refers to an outer scope:
environment = "production"
def target_environment():
environment = "staging"
return environment
print(target_environment()) # staging
print(environment) # production
Prefer function parameters and return values over global state. Explicit data flow is easier to test and safer when automation runs concurrently.
Quick Interview Answer
“A Python variable is just a name bound to an object — there’s no separate declaration step, no type annotation required.
x = y = z = 0binds all three names to the same object;a, b, c = 1, 2, 3unpacks three values in one line. Because a name is just a label, reassigning it to a different type is perfectly legal — that’s what ‘dynamically typed’ means.”
Troubleshooting Checklist
When a variable behaves unexpectedly:
- Print or inspect its value and
type(value). - Check whether the name was accidentally rebound later in the function.
- For mutable values, inspect whether two names refer to the same object with
is. - Use
==for value comparison andis Nonefor the absence of a value. - Check unpacking counts when Python raises
ValueError. - Review environment-variable defaults and validate required configuration before use.
Interview Follow-up Questions
- Is a Python variable a box, a pointer, or a name bound to an object?
- What is the difference between rebinding and mutating an object?
- Why does
x = y = []create an aliasing problem? - What is the difference between
==andis? - Do type annotations enforce types at runtime?
- Why is
Nonea safer default sentinel than an empty mutable list?
Common Mistakes
- Assuming
x = y = []creates two separate lists — it creates one list object that both names point to, so mutating it through either name affects both. - Forgetting that reassignment doesn’t mutate the old value — it just points the name at a new object, leaving the old one unchanged (and eventually garbage-collected if nothing else references it).
- Using a mutable list or dictionary as a default function argument — the same object persists across calls.
- Using
isfor ordinary value comparison — use==unless object identity is specifically what you need. - Believing annotations enforce types — they document intent and support tools, but Python does not enforce them automatically.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form