• Variables in Python are names pointing to objects in memory, not boxes that store values.
  • CPython uses reference counting + a cycle-detecting garbage collector.
  • Mutable vs immutable objects change how reassignment and function calls behave.
  • Use == for value equality and is for identity checks.
  • CPython optimizes small ints and strings through interning and performs constant folding at compile time.

1) Variables Are Names, Not Boxes

Most beginners imagine variables as “containers” holding values. In Python, a variable is actually a name that references an object in memory.

Think: Name → Object (type, value, refcount)

2) Memory References in Python

Assigning a variable binds a name to an object:

x = 10 y = x # y points to the same object as x  print(x, y) # 10 10 print(id(x), id(y)) # same identity 

Rebinding creates a new link:

x = [1, 2, 3] y = x y.append(4) print(x) # [1, 2, 3, 4] 

Why it matters: Mutating objects via one name can affect other names referencing the same object.

3) Reference Counting in CPython

CPython tracks how many references point to an object. When the count hits 0, the object is deallocated.

import sys a = [1, 2, 3] print(sys.getrefcount(a)) # +1 due to temporary reference in function b = a print(sys.getrefcount(a)) # increased del b print(sys.getrefcount(a)) # back down 

Note: PyPy and other Python implementations use different GC strategies.

4) Garbage Collection & Cycles

Reference counting alone can’t reclaim cycles (A → B → A). Python’s cycle collector handles this:

import gc class Node: def __init__(self): self.ref = None a, b = Node(), Node() a.ref, b.ref = b, a del a, b collected = gc.collect() print("Collected:", collected) 

Tip: Avoid __del__ in cyclic objects; it can delay collection.

5) Dynamic vs Static Typing

Python is dynamically typed (names can point to any type) and strongly typed (no silent coercion):

x = 10 x = "ten" # fine # x + 5 # TypeError 

Type hints help humans and IDEs, not the interpreter.

6) Variable Reassignment

Reassignment changes the binding, not the object:

x = 10 old_id = id(x) x = 20 print(old_id == id(x)) # False 

For mutable objects, you can mutate or rebind:

nums = [1, 2] nums = nums + [3] # new list nums.append(4) # same list 

7) Mutability Explained

  • Immutable: int, float, bool, str, tuple, frozenset
  • Mutable: list, dict, set
s = "hi" s += "!" # new string object  d = {"a":1} d["b"] = 2 # same dict object 

Tuples can contain mutable objects:

t = ([], 42) t[0].append("boom") print(t) # (['boom'], 42) 

8) Functions, Arguments & Mutability

Python passes object references to functions:

def mutate(lst): lst.append(99) def rebind(lst): lst = lst + [99] a = [1, 2, 3] mutate(a) # [1, 2, 3, 99] rebind(a) # still [1, 2, 3, 99] 

Avoid mutable default arguments:

def good(x, bucket=None): if bucket is None: bucket = [] bucket.append(x) return bucket 

9) Shared References Gotchas

Two names referencing the same mutable object share changes:

a = [1,2]; b = a; b.append(3) print(a) # [1,2,3] 

Copy carefully:

import copy x = [[1],[2]] y = copy.copy(x) # shallow z = copy.deepcopy(x) # deep 

10) Equality: == vs is

  • == → value equality
  • is → identity (same object)
x = [1,2,3]; y = [1,2,3] print(x==y, x is y) # True, False  a = None; b = None print(a is b) # True 

Use is None for sentinel checks.

11) Everything is an Object

Names live in namespaces (dict-like):

x = 42 def f(): pass print(type(x), type(f)) print(globals().keys()) 

LEGB rule: Local → Enclosing → Global → Builtin

12) CPython Optimizations: Integer Interning

Small integers are often shared:

a = 256; b = 256 print(a is b) # True  a = 257; b = 257 print(a is b) # Might be False 

13) CPython Optimizations: String Interning

Compile-time literals and identifiers may be interned:

import sys u = "".join(["status","_ok"]) v = "".join(["status","_ok"]) print(u is v) # False u = sys.intern(u); v = sys.intern(v) print(u is v) # True 

14) Constant Folding & Peephole Optimizations

Python precomputes simple expressions at compile-time:

x = 2*10 # folded to 20 y = ("a"+"b") # folded to "ab" 

import dis def demo(): return 2*10 dis.dis(demo) 

Big-O matters more than peephole tricks.

Quick Checklist

  • [ ] Names point to objects, not values.
  • [ ] == for value, is for identity.
  • [ ] Mutables vs immutables.
  • [ ] Avoid mutable defaults.
  • [ ] Copy carefully (copy() vs deepcopy()).
  • [ ] Refcount + GC for cycles.
  • [ ] Interning is an optimization detail.
  • [ ] Clarity > micro-optimizations.

Wrap-Up

The golden rule: Python names point to objects. Once you internalize that, mutability, GC, equality, and optimization behavior all make sense.


Source: DEV Community.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.