Memory model

Let’s start with something simple

  • welcome = "Hello world"
  • Names refer to values
  • Many names can refer to one value
    greeting = welcome

Immutable objects

  • year = 2026
    this = year
    year = 2026
    this = year
    
    year = 2027
  • Names are reassigned independently of other names

Mutable objects

  • names = ["Andrey", "Carlos", "Nikita"]
  • Each list element is a reference too
  • names.append("Olesia")

Mutable objects

names = ["Andrey", "Carlos"]
teachers = names
names = ["Andrey", "Carlos"]
teachers = names

names.append("Olesia")
print(teachers)

Immutable objects

value = 23
another = value

value += 50
print(another)

value += 50 is value = value + 50, which is value = value.__add__(50)

Assignments

  • Assignment makes a name refer to a value
  • The value may be a constant or come from another name
  • Assignment never copies data — it only creates a reference
  • Changes to a value are visible through every name that refers to it
  • A value stays in the memory pool until nothing refers to it
  • Garbage collection is a separate topic — Keywords: reference counting, sys.getrefcount, __del__, circular references, weakref
  • Myth: Python treats assignments to mutable and immutable values differently — it does not

References

  • References can be more than just variables
  • For instance, names[1] is also a reference
names = ["Olesia", "Carlos"]
names = ["Olesia", "Carlos"]
names[1] += " Manuel"

References

In all these cases, X becomes a reference to a value in memory

X = ...
for X in ...
[... for X in ...]
(... for X in ...)
{... for X in ...}
class X(...):
def X(...):
def fn(X): ... ; fn(12)
with ... as X:
try ... except ... as X:
import X
from ... import X
import ... as X
from ... import ... as X

Functions

vat = 0.2

def calculate_cost(quantity: int, price: float) -> float:
    return quantity * price * (1 + vat)

print(calculate_cost(5, 1.5))
  • When are quantity and price created?
  • Python passes object references to functions
  • vat is a global variable, available both inside and outside the function

Functions

def append_twice(where: list, what: int):
    where.append(what)
    where.append(what)
    return where

print(append_twice([10, 15], 4))
def append_twice(where: list, what: int):
    where.append(what)
    where.append(what)
    return where

items = [10, 15]
print(append_twice(items, 4))
print(items)

Functions

def append_twice(where: list, what: int):
    where = where + [what, what]
    return where

items = [10, 15]
print(append_twice(items, 4))
print(items)

Dynamic Typing

  • Any variable can refer to any value at any time
    value = 123
    value = "Hello world"
  • Variables have no type, values have no scope
  • Values can’t be deleted, only references to them can
    nums = [1, 2, 3]
    del nums

Duck typing

  • If it walks like a duck, and it quacks like a duck, then it must be a duck
  • You don’t have to specify a type for a variable — if it works, it works
  • def sum(a, b):
        return a + b
  • You might have heard about interfaces in other programming languages — there are no interfaces in Python (there are Protocols and Abstract classes though)

Duck typing

def sum(a: int, b: int) -> int:
    return a + b

print(sum("One ", "Two"))

Nested lists

All lists in Python are one-dimensional, but you can create a one-dimensional list of one-dimensional lists

a = [[1, 2, 3], [5, 6, 7]]
print(a[1][2])

Nested lists

a = [[1, 2, "Hello"], ["world", 10], 15]
print(a)
a[1] = [20, "updated"]

Nested lists

values = [0] * 10
print(values)
values = [[0] * 5] * 10
print(values)
values[1][2] = "X"
print(values)

Nested lists

values = [[0] * 5 for _ in range(10)]
values[1][2] = "X"
print(values)

Dicts

cart = {
    "fruits": ["apple", "banana", "orange"],
    "vegetables": ["carrot", "potato", "onion"],
    "drinks": ["water", "juice"]
}

How can you find where a value is stored?

  • Just use id(variable)!
  • a = [100, 500]
    b = a
    print(id(a), id(b))
  • a = [100, 500]
    b = [100, 500]
    print(id(a), id(b))
  • a = 100500
    b = 100500
    print(id(a), id(b))

Some tricks bonus

number1 = int("100500")
number2 = int("100500")
print(id(number1) == id(number2)) # same as "number1 is number2"
number1 = int("100")
number2 = int("100")
print(id(number1) == id(number2))

Task: identify the range of pre-cached numbers [answer]