Skip to main content

Command Palette

Search for a command to run...

03_Understanding Variables: Labels vs. Boxes Explained

Published
4 min read
03_Understanding Variables: Labels vs. Boxes Explained
S

Full-stack developer documenting what I’m learning as I go. This space is all about tech, understanding how things work, and writing things down as they start to make sense.

If you are coming from languages like C++ or Java, Python variables are probably gaslighting you. They look the same, they use the same = syntax, but they behave completely differently under the hood.

I realized I had the wrong mental model. In Python, variables aren't boxes. They are sticky notes.

In this guide, let’s explore what actually happens in memory when you create a variable, and why the "Box vs. Label" distinction is the most important concept in Python.


1. The Fundamental Truth: Labels, Not Boxes

In statically typed languages (like C), a variable is a specific chunk of memory reserved for a specific type of data. It’s a literal container.

In Python, variables are just labels (or name tags) that point to objects living somewhere in the heap memory.

The Visualization

When you write x = 5, you aren't putting "5" into a box named x.

  1. Python creates an integer object 5 somewhere in memory.

  2. It writes "x" on a string (a reference) and ties it to that object.

x = 5  # x is a label attached to the object 5

The Proof: We can use the id() function (which reveals the memory address) to prove this.

x = 42
print(f"Address of x: {id(x)}")
# Output: 14071... (Some memory address)

If you reassign x = 43, you aren't changing the value inside the box. You are ripping the "x" tag off the 42 object and sticking it onto a totally new object 43 at a different address.


2. Assignment = Aliasing (The "Two Tags" Rule)

Because variables are just references, assignment in Python behaves uniquely. This is where most beginners get bitten by bugs.

When you do reference = original, you are not copying the data. You are simply sticking a second label onto the same existing object.

original = [1, 2, 3]
reference = original  # Both now point to the same list

reference.append(4) # Modifying one affects the other

print(original)   
# Output: [1, 2, 3, 4] -> It changed!

My Takeaway: If I want a true independent copy, I have to explicitly ask for one using original.copy() or slicing original[:].


3. Dynamic Typing & "Duck Typing"

Since variables are just labels, they don't care what they are attached to. A label can point to a cat one second and a dog the next.

x = 10      # x labels an integer
x = "Hello" # x now labels a string

This leads to Python's famous philosophy: Duck Typing. "If it walks like a duck and quacks like a duck, it must be a duck."

You don't need to specify that a function accepts a File object. As long as the object passed has a .read() method, Python is happy. This allows for incredible flexibility and rapid prototyping, but requires discipline to keep track of what your variables actually hold.


4. Power Feature: Unpacking

One of the coolest features I found is Extended Unpacking. It allows you to grab specific elements from a list using the asterisk * operator.

# Swapping without a temporary variable (The Pythonic Way)
a, b = 10, 20
a, b = b, a  # Swaps values instantly

# Grabbing the middle of a list
first, *middle, last = [1, 2, 3, 4, 5]

print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

5. Scope: The LEGB Rule

Understanding where a variable lives is crucial to avoiding bugs. Python resolves names using the LEGB Rule, searching scopes in this specific order:

  1. Local (Inside the current function)

  2. Enclosing (Inside enclosing functions/nested functions)

  3. Global (Module level)

  4. Built-in (Python's keywords like list or str)

The Common Pitfall: If you want to modify a global variable inside a function, you must use the global keyword. Otherwise, Python just creates a new local variable with the same name, leaving the global one untouched.

counter = 0

def update():
    global counter # Tell Python we mean the global one
    counter += 1

6. A Reminder on Mutability

As we discussed in the previous post, this "Label" system explains the infamous Mutable Default Argument trap.

If you use a list as a default argument, that list is created once when the function is defined. The label stays attached to that same list forever.

❌ The Wrong Way:

def add_item(item, items=[]): 
    items.append(item)
    return items
# This list will persist and grow across different function calls!

✅ The Correct Way:

def add_item(item, items=None):
    if items is None:
        items = []  # Create a new list every time
    items.append(item)
    return items

Conclusion: PEP 8 and Best Practices

To wrap up, treating variables as labels rather than boxes unlocked a deeper understanding of Python for me. To keep things clean, I try to follow PEP 8 naming conventions:

  • Variables/Functions: snake_case (e.g., user_name)

  • Constants: SCREAMING_SNAKE_CASE (e.g., MAX_RETRIES)

  • Classes: PascalCase (e.g., UserAccount)

  • Private Members: _leading_underscore (e.g., _internal_value)

The Python Log: Learning in Public

Part 3 of 4

I’m sharing my journey from being Python-curious to Python-capable. This isn’t a textbook or a step-by-step course—it’s just me writing down the things I’m learning, the parts that confused me, and the insights that finally made things click.

Up next

04_Numbers in Python

Understanding How Python Represents and Works With Numbers