Chained Assignment¶
Mental Model
a = b = c = value evaluates value once and binds all three names to that single object. With immutables like integers this is harmless, but with mutables like lists it means all names share the same object -- mutating through one name is visible through all of them.
Basic Chaining¶
1. Simple Case¶
```python a = b = c = 1 print(f"{a = }, {b = }, {c = }")
a = 1, b = 1, c = 1¶
```
2. Single Object¶
All names refer to same object:
python
a = b = c = [1, 2, 3]
print(f"{a is b is c = }") # True
print(f"{id(a) == id(b) == id(c) = }") # True
Evaluation Order¶
1. Right-to-Left¶
Expression evaluated right-to-left:
```python a = b = c = 1 + 2 + 3
1. Compute: 1 + 2 + 3 = 6¶
2. Create object: 6¶
3. Assign: c, then b, then a¶
```
2. Bytecode Evidence¶
```python import dis
def f(): a = b = c = 1
dis.dis(f) ```
Shows:
LOAD_CONST 1DUP_TOP(duplicate)DUP_TOP(duplicate)STORE_NAME(c, b, a)
Shared Mutability¶
1. Lists¶
```python x = y = z = [1, 2, 3]
Modify through one¶
x.append(4)
All see change¶
print(f"{x = }") # [1, 2, 3, 4] print(f"{y = }") # [1, 2, 3, 4] print(f"{z = }") # [1, 2, 3, 4] ```
2. Dictionaries¶
```python a = b = c = {'key': 'value'}
a['new'] = 'data'
print(b) # {'key': 'value', 'new': 'data'} print(c) # {'key': 'value', 'new': 'data'} ```
Immutable Objects¶
1. No Issue¶
```python x = y = z = 42
x = 100 # Rebinds x only
print(f"{x = }") # x = 100 print(f"{y = }") # y = 42 print(f"{z = }") # z = 42 ```
2. String Example¶
```python a = b = c = "hello"
a = "world" # Rebinds a
print(f"{a = }") # a = 'world' print(f"{b = }") # b = 'hello' print(f"{c = }") # c = 'hello' ```
Best Practices¶
1. Use for Immutables¶
```python
Safe: immutable¶
MAX = DEFAULT = LIMIT = 100 ```
2. Avoid for Mutables¶
```python
Dangerous¶
x = y = z = [] # Same list!
Better¶
x = [] y = [] z = [] # Three lists ```
3. Default Arguments¶
```python
Don't do this!¶
def bad(element, target=x=y=[]): target.append(element) return target
Better¶
def good(element, target=None): if target is None: target = [] target.append(element) return target ```
Identity Checks¶
1. Verify Sharing¶
```python a = b = c = [1, 2, 3]
print(f"a is b: {a is b}") print(f"b is c: {b is c}") print(f"a is c: {a is c}")
All True¶
print(f"IDs: {id(a)}, {id(b)}, {id(c)}")
All same¶
```
2. Break Sharing¶
```python a = b = c = [1, 2, 3]
Rebind one name¶
a = [1, 2, 3] # New object
print(f"a is b: {a is b}") # False print(f"b is c: {b is c}") # True ```
Use Cases¶
1. Constants¶
```python
Good use case¶
ZERO = NULL = EMPTY = 0 ```
2. Counters¶
```python
Acceptable¶
wins = losses = draws = 0
wins += 1 # Only affects wins ```
3. Coordinates¶
```python
OK for numbers¶
x = y = z = 0.0
Dangerous for lists¶
positions = velocities = [0, 0, 0]¶
```
Exercises¶
Exercise 1. Use chained assignment to initialize three counter variables to zero. Increment only one of them and show the others remain unchanged. Explain why this is safe.
Solution to Exercise 1
```python wins = losses = draws = 0 wins += 1 print(f"{wins=}, {losses=}, {draws=}")
wins=1, losses=0, draws=0¶
```
This is safe because integers are immutable. wins += 1 rebinds wins to a new integer object, leaving losses and draws bound to the original 0.
Exercise 2.
Demonstrate the danger of chained assignment with mutable objects. Assign a = b = c = [], then append to a and show that b and c are also affected.
Solution to Exercise 2
python
a = b = c = []
a.append(1)
print(f"{a=}") # [1]
print(f"{b=}") # [1] (same object!)
print(f"{c=}") # [1] (same object!)
print(a is b is c) # True
All three names point to the same list object. Mutating through any name affects all of them.
Exercise 3.
Write code that creates three independent empty lists (not sharing references) in a single line without chained assignment. Verify with is that they are different objects.
Solution to Exercise 3
python
a, b, c = [], [], []
print(a is b) # False
print(b is c) # False
Each [] literal creates a new list object. Tuple unpacking assigns each name to a separate object.