Python Variables and Type System
Understand variables, dynamic typing, scoping, and reference semantics in Python.
Variables in Python are references to objects in memory. Unlike languages like C++ or Java, Python does not require explicit type declarations. The type of a variable is determined at runtime based on the object it references.
Variables as References
Understanding that Python variables are references — not containers — prevents a whole class of bugs, especially when working with mutable objects like lists and dicts. When two variables point to the same object, a mutation through one is visible through the other.
# Create an integer object in memory and point 'x' to it
x = 42
# Point 'y' to the same object as 'x' — no copy is made
y = x
# id() returns the memory address of the object — both are the same
print(id(x) == id(y)) # True: x and y reference the same object
In Python, numbers and strings are immutable. When you reassign a variable, Python creates a new object rather than modifying the existing one. This is why reassigning x doesn’t affect y:
# Creates a new integer object (43) and points x to it
x = x + 1
print(x) # 43 — x now references a new object
print(y) # 42 — y still references the original object
Naming Conventions (PEP 8)
Consistent naming is one of the easiest ways to make code readable for your teammates — and your future self. Python’s PEP 8 style guide is the widely accepted standard across the ecosystem.
- Use
snake_casefor variable and function names. - Use
CAPITAL_SNAKE_CASEfor global constants. - Avoid using Python keywords like
list,str, orclassas variable names — they shadow the built-in types and cause confusing errors.
# Good — snake_case for variables, SCREAMING_SNAKE_CASE for constants
user_name = "Alice"
MAX_RETRY_COUNT = 3
# Bad — shadows the built-in list type, causes subtle bugs
list = [1, 2, 3]
The Scope Resolution (LEGB)
Python resolves variable names by searching four scopes in order. Understanding LEGB explains why a variable defined inside a function isn’t visible outside it, and why you need global or nonlocal to modify outer variables from inner functions.
- Local (L): Names assigned inside a function body.
- Enclosing (E): Names in the local scope of enclosing functions (nonlocal).
- Global (G): Names assigned at the top-level of a module file.
- Built-in (B): Names preloaded into the built-in namespace (e.g.,
len,range).
# Global variable — visible everywhere in this module
prefix = "User: "
def greet(name):
# Local variable — only accessible inside greet()
message = f"Hello, {name}"
def format_output():
# Enclosing scope: format_output() can read 'message' and 'prefix'
# even though they weren't defined here
return f"{prefix}{message}"
return format_output()
print(greet("Alice")) # "User: Hello, Alice"
If you need to assign to a variable in an outer scope, use the global or nonlocal keyword to tell Python which scope you mean:
count = 0
def increment():
global count # without this, Python would create a new local 'count'
count += 1
increment()
print(count) # 1