Upgrade to Pro — share decks privately, control downloads, hide ads and more …

What does = do? (Talk from Euro Python 2025)

What does = do? (Talk from Euro Python 2025)

We use assignment all the time in Python. But what really happens when we use an = in our code? The answer is: It depends. In this talk, I'll explore what happens when we assign a value to a variable, when we mutate an existing value, and when we assign to an attribute. Along the way, we'll explore variable scopes, byte codes, and descriptors. By the end of this talk, you'll appreciate all the hard work that the little = sign is doing, and the remarkable stuff that happens behind the scenes when you use it.

Avatar for Reuven M. Lerner

Reuven M. Lerner

July 18, 2025
Tweet

More Decks by Reuven M. Lerner

Other Decks in Technology

Transcript

  1. What does = do? Reuven M. Lerner • https://LernerPython.com I

    teach Python and Pandas 2 In business since 1995 2
  2. What does = do? Reuven M. Lerner • https://LernerPython.com 3

    Courses • Exercises • Of f ice hours • Discord • Projects 3
  3. What does = do? Reuven M. Lerner • https://LernerPython.com •

    We use = to assign • Not the same as the mathematical = • Rather, it means: • Evaluate the right side • Assign the value you get to the variable on the left The assignment operator 10 10
  4. What does = do? Reuven M. Lerner • https://LernerPython.com •

    We always evaluate the right side f irst! • The result of that evaluation is assigned Right before left! 11 x = 75 + 25 x = 100 11
  5. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Because it’s an easy-to-understand metaphor • Also: For C programmers, it’s accurate! • Variables are aliases for memory locations • When you assign, you’re putting a value in memory Why would we think this? 13 13
  6. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Variables refer to values • Not memory locations • Not other variables • Values are anonymous Python isn’t like this 15 15
  7. What does = do? Reuven M. Lerner • https://LernerPython.com If

    values are nouns, then variables are pronouns 16 16
  8. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    dis.dis(bytecode) 0 RESUME 0 1 LOAD_CONST 0 (100) STORE_NAME 0 (x) RETURN_CONST 1 (None) Disassemble “x = 100” 19 19
  9. What does = do? Reuven M. Lerner • https://LernerPython.com x

    = 100 What really happens when we assign? 20 20
  10. What does = do? Reuven M. Lerner • https://LernerPython.com x

    = 100 y = x x = 200 print(y) What does this code do? 21 Variable “x” refers to int 100 Variable “y” refers to whatever x refers to … not to x! Variable “x” refers to int 200 Prints 100, because y still refers to 100 21
  11. What does = do? Reuven M. Lerner • https://LernerPython.com In

    the Python Tutor… 22 x = 100 y = x x = 200 print(y) 22
  12. What does = do? Reuven M. Lerner • https://LernerPython.com •

    The = operator isn’t the only way to assign to a variable! • Some keywords also do it def class import • We’re going to ignore those — but realize that they all assign to variables, as well as creating function, class, and module values. Other ways to assign 23 23
  13. What does = do? Reuven M. Lerner • https://LernerPython.com •

    So far, we’ve assigned to global variables • Python keeps track of globals in a dict • (See them with globals() !) Global variables 24 24
  14. What does = do? Reuven M. Lerner • https://LernerPython.com •

    But… globals are problematic! • One part of the program can step on another part • In Python, only functions create local variables • No functions? No locals • Indentation doesn’t create local variables Local variables 25 25
  15. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Python’s compiler notices variable assignments in a function • It stores them in the function object def mysum(a, b): total = a + b return total >>> mysum.__code__.co_varnames ('a', 'b', 'total') “Declaring” local variables 26 26
  16. What does = do? Reuven M. Lerner • https://LernerPython.com •

    If you assign to a variable in a function, it’s local • Even if there’s a global of the same name • STORE_FAST means: store in a local variable Local variables 27 27
  17. What does = do? Reuven M. Lerner • https://LernerPython.com def

    myfunc(): x = 100 >>> dis.dis(myfunc) 1 RESUME 0 2 LOAD_CONST 1 (100) STORE_FAST 0 (x) RETURN_CONST 0 (None) Let’s disassemble! 28 28
  18. What does = do? Reuven M. Lerner • https://LernerPython.com •

    The “global” keyword means: when we assign, we’ll assign to the global • No local variable is created • What if we want to assign to a global? 29 29
  19. What does = do? Reuven M. Lerner • https://LernerPython.com def

    myfunc(): global x x = 100 >>> dis.dis(myfunc) 1 RESUME 0 3 LOAD_CONST 1 (100) STORE_GLOBAL 0 (x) RETURN_CONST 0 (None) Disassembling “global” 30 30
  20. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Depending on context, = can thus mean 1. Assign to a global variable (outside of a function) 2. Assign to a global variable (inside of a function, via “global”) 3. Assign to a local variable (inside of a function) = means 3 different things 31 31
  21. What does = do? Reuven M. Lerner • https://LernerPython.com •

    We’ve already seen this: def mysum(x, y): total = x + y return total • What if we want to know how many times mysum was run? • We can rewrite it as an inner function! • “counter” can be an integer in the outer function that tracks how often we’ve calculated sums What about inner functions? 32 32
  22. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Running “outer” returns “mysum” • “counter” — a local in the outer function — sticks around across calls def outer(): counter = 0 def mysum(x, y): counter += 1 total = x + y print(f'[{counter}] {x} + {y} = {total}') return total return mysum Nested functions 33 33
  23. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    sum_counter = outer() >>> sum_counter(10, 3) UnboundLocalError: cannot access local variable 'counter' where it is not associated with a value Just one problem… 34 34
  24. What does = do? Reuven M. Lerner • https://LernerPython.com •

    += is assignment, not mutation • As always, assigning to “counter” creates a local variable • But we want to assign to “counter” in the outer function! • We can’t use the “global” keyword, because it isn’t • We can use the “nonlocal” keyword • Assignment then binds to the local in the enclosing function += assigns to a local! 35 35
  25. What does = do? Reuven M. Lerner • https://LernerPython.com def

    outer(): counter = 0 def mysum(x, y): nonlocal counter counter += 1 total = x + y print(f'[{counter}] {x} + {y} = {total}') return total return mysum Use “nonlocal” to solve this 36 36
  26. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    sum_counter = outer() >>> sum_counter(10, 3) [1] 10 + 3 = 13 13 >>> sum_counter(20, 5) [2] 20 + 5 = 25 25 Let’s try it! 37 37
  27. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    dis.dis(sum_counter) -- COPY_FREE_VARS 1 3 RESUME 0 5 LOAD_DEREF 3 (counter) LOAD_CONST 1 (1) BINARY_OP 13 (+=) STORE_DEREF 3 (counter) 6 LOAD_FAST_LOAD_FAST 1 (x, y) BINARY_OP 0 (+) STORE_FAST 2 (total) Examining (some) opcodes 38 38
  28. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Depending on context, = can thus mean 1. Assign to a global variable (outside of a function) 2. Assign to a global variable (inside of a function, via “global”) 3. Assign to a local variable (inside of a function) 4. Assign to a non-local variable (in the enclosing function) = means 4 different things 39 39
  29. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    mylist = [10, 20, 30] >>> mylist[1] = 999 What happens here? 41 41
  30. What does = do? Reuven M. Lerner • https://LernerPython.com We

    aren’t assigning. We are mutating. 42 42
  31. What does = do? Reuven M. Lerner • https://LernerPython.com •

    The variable refers to the same data structure • But the data structure changes Mutating is different! 43 43
  32. What does = do? Reuven M. Lerner • https://LernerPython.com Consider

    this 44 Assignment changes this arrow (the name-to- value binding) Mutation changes this arrow (the value Itself) 44
  33. What does = do? Reuven M. Lerner • https://LernerPython.com def

    normal_mutate(data): data[1] = 2 >>> dis.dis(normal_mutate) 1 RESUME 0 2 LOAD_CONST 1 (2) LOAD_FAST 0 (data) LOAD_CONST 2 (1) STORE_SUBSCR RETURN_CONST 0 (None) STORE_SUBSCR 45 45
  34. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Depending on context, = can thus mean 1. Assign to a global variable (outside of a function) 2. Assign to a global variable (inside of a function, via “global”) 3. Assign to a local variable (inside of a function) 4. Assign to a non-local variable (in the enclosing function) 5. Mutate the value on the left (list, dict, or your own object) = means 5 different things 46 46
  35. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Variables • Attributes • A private dict on each Python object (or value) • When you say “a.b”, you mean “attribute b on object a” • How to distinguish? • Variables live in namespaces • Attributes live in values (objects) Python has two storage systems 47 47
  36. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Yes — on nearly everything! • The STORE_ATTR opcode assigns >>> import random >>> random.x = 100 >>> print(random.x) 100 Can we assign to an attribute? 48 48
  37. What does = do? Reuven M. Lerner • https://LernerPython.com def

    set_random_x(): random.x = 1234 >>> dis.dis(set_random_x) 1 RESUME 0 2 LOAD_CONST 1 (1234) LOAD_GLOBAL 0 (random) STORE_ATTR 1 (x) RETURN_CONST 0 (None) What bytecode is running? 49 49
  38. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Depending on context, = can thus mean 1. Assign to a global variable (outside of a function) 2. Assign to a global variable (inside of a function, via “global”) 3. Assign to a local variable (inside of a function) 4. Assign to a non-local variable (in the enclosing function) 5. Mutate the value on the left (list, dict, or your own object) 6. Assign a new value to the attribute on the left = means 6 different things 50 50
  39. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Some attributes are special • That is: When we set them, a method is invoked • And when we read from them, another method is invoked • No () are needed! • These are properties Properties 51 51
  40. What does = do? Reuven M. Lerner • https://LernerPython.com class

    Car: def __init__(self): self._speed = 0 @property def speed(self): print('In speed getter') return self._speed @speed.setter def speed(self, new_speed): print('In speed setter') if new_speed < 0: raise ValueError('Too slow!') if new_speed > 140: raise ValueError('Too fast!') self._speed = new_speed Property example 52 52
  41. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    c = Car() >>> c.speed In speed getter 0 c.speed = 100 In speed setter c.speed In speed getter 100 Using our property 53 How can this be? Assignment invokes a method?!? 53
  42. What does = do? Reuven M. Lerner • https://LernerPython.com def

    set_speed(): c.speed = 100 >>> dis.dis(set_speed) 1 RESUME 0 2 LOAD_CONST 1 (100) LOAD_GLOBAL 0 (c) STORE_ATTR 1 (speed) RETURN_CONST 0 (None) Bytecodes are the same 54 54
  43. What does = do? Reuven M. Lerner • https://LernerPython.com •

    If a class has an attribute • And that attribute is a property • And we assign via the instance • Then we invoke the property’s “setter” method Attributes have some magic 55 55
  44. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Properties are simple versions of descriptors. • The descriptor protocol: • De f ine a descriptor class with __get__ and __set__ methods • De f ine an owner class, and set a class attribute to an instance of the descriptor class • If we assign to the descriptor via an instance, the __set__ method is invoked Descriptors 56 56
  45. What does = do? Reuven M. Lerner • https://LernerPython.com class

    Descriptor: def __init__(self): self.value = 10 def __get__(self, obj, objtype=None): print(f'In __get__, {self=}, {obj=}, {objtype=}') return self.value def __set__(self, obj, new_value): print(f'In __set__, {self=}, {obj=}, {new_value=}') self.value = new_value De f ining a descriptor 57 57
  46. What does = do? Reuven M. Lerner • https://LernerPython.com class

    Owner: d = Descriptor() o = Owner() Using the descriptor 58 Class attribute, an instance of Descriptor 58
  47. What does = do? Reuven M. Lerner • https://LernerPython.com >>>

    o.d = 20 • o is an instance of Owner • d is a class attribute on Owner • We assign to d via the instance, o • d is an instance of Descriptor • Descriptor has a __set__ method • Assignment runs __set__! In __set__, self=<__main__.Descriptor object at 0x1059a9fd0>, obj=<__main__.Owner object at 0x1059aa3c0>, new_value=20 Assigning to the descriptor 59 59
  48. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Instead of Descriptor, we’ll call it Speed • Instead of Owner, we’ll call it Car • Instead of d, we’ll call it speed • In other words, we’ve recreated our property… • …but now we can use it in multiple classes! Let’s make this more concrete 61 61
  49. What does = do? Reuven M. Lerner • https://LernerPython.com class

    Speed: def __init__(self): self._speed = 10 def __get__(self, obj, objtype=None): print(f'In __get__, {self=}, {obj=}, {objtype=}') return self._speed def __set__(self, obj, new_speed): print(f'In __set__, {self=}, {obj=}, {new_speed=}') if new_speed < 0: raise ValueError('Too slow!') if new_speed > 140: raise ValueError('Too fast!') self._speed = new_speed Our Speed descriptor 62 62
  50. What does = do? Reuven M. Lerner • https://LernerPython.com class

    Car: speed = Speed() c = Car() c.speed = 100 Putting Speed in the car 63 Class attribute, an instance of Speed 63
  51. What does = do? Reuven M. Lerner • https://LernerPython.com •

    What if we have two instances of Car? car1 = Car() car2 = Car() • They share a Speed descriptor • That instance of Speed stores its value in self.value • So assigning to car1.speed changes Car.speed._speed • And assigning to car2.d also changes Car.speed._speed • Uh oh! 😱 A problem 65 65
  52. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Don’t store a single value in the Speed descriptor • Instead, use a dict! • Keys are instances of Car (or whatever class is using Speed) • Values are the speci f ic values for that descriptor “owner” The solution 67 67
  53. What does = do? Reuven M. Lerner • https://LernerPython.com class

    Speed: def __init__(self): self.values = {} def __get__(self, obj, objtype=None): print(f'In __get__, {self=}, {obj=}, {objtype=}') return self.values[obj] def __set__(self, obj, new_speed): print(f'In __set__, {self=}, {obj=}, {new_speed=}') if new_speed < 0: raise ValueError('Too slow!') if new_speed > 140: raise ValueError('Too fast!') self.values[obj] = new_speed Updated Speed descriptor 68 68
  54. What does = do? Reuven M. Lerner • https://LernerPython.com •

    We assign to car1.speed or car2.speed • We’re assigning to a class attribute (Car.speed) via the instance • That class attribute, of type Speed, has a __set__ method • Assigning to car1.speed gets transformed into a call to __set__ • self is the instance of Speed, aka Car.speed • obj is the instance of Car, aka car1 or car2 • new_speed is the value on the right of assignment • The effect is to mutate the dict in Speed.values In other words 69 69
  55. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Do things this way, and you’ll have a memory leak! • The dict will always refer to each instance • And referred-to objects are never garbage collected • The standard solution: Use a WeakKeyDict for storing values • That way, the descriptor’s dict won’t keep the value from being freed Memory leaks 71 71
  56. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Depending on context, = can thus mean 1. Assign to a global variable (outside of a function) 2. Assign to a global variable (inside of a function, via “global”) 3. Assign to a local variable (inside of a function) 4. Assign to a non-local variable (in the enclosing function) 5. Mutate the value on the left (list, dict, or your own object) 6. Assign a new value to the attribute on the left 7. Cause the __set__ method in a descriptor to f ire, if we assign to a class attribute via an instance = means 7 different things! 72 72
  57. What does = do? Reuven M. Lerner • https://LernerPython.com 73

    Opcode Meaning STORE_NAME or STORE_GLOBAL Assign to a global (outside a function, or inside with “global”) STORE_FAST Assign to a local (inside a function) STORE_DEREF Assign to a local in an enclosing function STORE_SUBSCR Mutate a value STORE_ATTR Assign to an attribute — which might, if the attribute is a descriptor, result in the execution of its __set__ method 73
  58. What does = do? Reuven M. Lerner • https://LernerPython.com def

    assign_assign(): x = y = 5 >>> dis.dis(assign_assign) 1 RESUME 0 2 LOAD_CONST 1 (5) COPY 1 STORE_FAST_STORE_FAST 1 (x, y) RETURN_CONST 0 (None) Multiple assignment 74 74
  59. What does = do? Reuven M. Lerner • https://LernerPython.com def

    assign_assign_assign(): x = y = z = 5 >>> dis.dis(assign_assign_assign) 1 RESUME 0 2 LOAD_CONST 1 (5) COPY 1 STORE_FAST 0 (x) COPY 1 STORE_FAST_STORE_FAST 18 (y, z) RETURN_CONST 0 (None) What if we do three? 75 75
  60. What does = do? Reuven M. Lerner • https://LernerPython.com def

    unpacking2(): x,y = [10, 20] >>> dis.dis(unpacking2) 1 RESUME 0 2 LOAD_CONST 1 (10) LOAD_CONST 2 (20) BUILD_LIST 2 UNPACK_SEQUENCE 2 STORE_FAST_STORE_FAST 1 (x, y) RETURN_CONST 0 (None) What about unpacking? 76 76
  61. What does = do? Reuven M. Lerner • https://LernerPython.com def

    unpacking3(): x,y,z = [10, 20, 30] >>> dis.dis(unpacking3) 1 RESUME 0 2 BUILD_LIST 0 LOAD_CONST 1 ((10, 20, 30)) LIST_EXTEND 1 UNPACK_SEQUENCE 3 STORE_FAST_STORE_FAST 1 (x, y) STORE_FAST 2 (z) RETURN_CONST 0 (None) And with 3 elements… 77 77
  62. What does = do? Reuven M. Lerner • https://LernerPython.com •

    Assignment seems obvious and simple • But this one operator does many different things • It creates variables, and binds their names to values • It creates attributes, and binds their names to values • It mutates values • It f ires methods (in properties and descriptors) • It can even cause memory leaks, if you aren’t careful • These different things feel similar to us as people • But Python sees them as totally different Conclusion 78 78
  63. What does = do? Reuven M. Lerner • https://LernerPython.com •

    My courses: https://LernerPython.com • Python articles: https://BetterDevelopersWeekly • Pandas puzzles: https://BambooWeekly.com • YouTube: https://YouTube.com/reuvenlerner • Bluesky: @lernerpython.com Questions? Comments? 79 79