Python & Django

Python Variables: Names, Not Boxes

August 17, 2026
2 min read
1 views

Coming from other languages, it's easy to think of a variable as a labeled box holding a value. In Python, it's more accurate to think of a variable as a name pointing to an object living somewhere in memory. That distinction explains a lot of Python's behavior.

Variables Are References

x = [1, 2, 3]
y = x       # y points to the SAME list as x
y.append(4)
print(x)    # [1, 2, 3, 4] — x changed too!

x and y aren't separate copies — they're two names pointing at the same object. Assignment in Python never copies data; it just binds a name to an object.

Mutable vs Immutable Matters Here

This "shared reference" behavior only causes surprises with mutable objects (lists, dicts, sets). With immutable objects (int, str, tuple, float), you can't accidentally mutate shared state, because there's no way to change the object in place:

a = 5
b = a
b += 1
print(a)  # 5 — unaffected, because b += 1 creates a NEW int object

Dynamic Typing

Python variables don't have a fixed type — they can be rebound to any kind of object at any time:

value = 42
value = "now I'm a string"
value = [1, 2, 3]

The object has a type; the variable is just a label that can point to any object.

Scope: Where a Variable Lives

Python resolves names using the LEGB rule — Local, Enclosing, Global, Built-in:

x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)  # "local"
    inner()
    print(x)  # "enclosing"

outer()
print(x)  # "global"

Each level of nesting can shadow the name from an outer scope without affecting it.

global and nonlocal

By default, assigning to a variable inside a function creates a new local variable rather than modifying an outer one:

counter = 0

def increment():
    counter += 1  # UnboundLocalError! Python sees this as a local assignment

def increment_fixed():
    global counter
    counter += 1  # now this modifies the global variable

nonlocal does the same thing for enclosing (not global) scope, commonly used in closures.

A Common Pitfall: Mutable Default Arguments

def add_item(item, basket=[]):  # DANGER
    basket.append(item)
    return basket

print(add_item("apple"))   # ['apple']
print(add_item("banana"))  # ['apple', 'banana'] — the SAME list persists!

Default argument values are evaluated once, when the function is defined — not each time it's called. The fix is to use None as a sentinel:

def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

Key Takeaway

Python variables are names bound to objects, not containers holding values. Once that model clicks, behaviors that seem confusing at first — shared references, mutable defaults, scope shadowing — become predictable consequences of a single consistent rule.

Topics

Python
MAR

MD Abdur Rahim

Senior Python Developer helping teams ship backend systems and AI products — Django, FastAPI, LangChain, RAG pipelines, and cloud infra that hold up in production.

Comments (0)

Minimum 3 characters

0/1000

No comments yet

Be the first to share your thoughts!

Enjoyed this article?

Subscribe to my newsletter to receive updates on new blog posts, tech insights, and development tips.

No spam. Unsubscribe anytime. Read our Privacy Policy.