1. Identity, Equality, and Aliases
- Every Python object has an identity, a type, and a value. Only the value of an object may change over time. Python variables are like reference variables in Java, we can think of variables as labels with names attached to objects
- The real meaning of an object’s ID is implementation dependent. In CPython, id() returns the memory address of the object, but it may be something else in another Python interpreter. The key point is that the ID is guaranteed to be a unique integer label, and it will never change during the life of the object
1 | a = [1, 2, 3] |
2. Deep and Shallow Copies
2.1. Copies Are Shallow by Default
1 | # using the constructor or [:] produces a shallow copy |
2.2. Copy Arbitrary Objects
- The copy module provides the deepcopy and copy functions that return deep and shallow copies of arbitrary objects
- A deep copy may be too deep in some cases. For example, objects may refer to external resources or singletons that should not be copied. You can control the behavior of both copy and deepcopy by implementing the __copy__() and __deepcopy__() special methods
1 | class Driver: |
2.3. Function Parameters as References
- The only mode of parameter passing in Python is call by sharing, which means that each formal parameter of the function gets a copy of each reference in the arguments. In other words, the parameters inside the function become aliases of the actual arguments
1 | def f(a, b): |
2.4. Tricks Python Plays with Immutables
- For a tuple t, t[:] does not make a copy, but returns a reference to the same object. You also get a reference to the same tuple if you write tuple(t). The same behavior can be observed with instances of str, bytes, and frozenset. Note that fs[:] does not work if fs is a frozenset. But fs.copy() has the same effect
- For immutable literals (like tuples, strings, and some integers), CPython often reuses objects as an optimization. For mutable objects (lists, dictionaries, sets), it must create a new object each time
1 | t1 = (1, 2, 3) |
3. del and Garbage Collection
- del is not a function, it’s a statement. We write del x and not del(x)—although the latter also works, but only because the expressions x and (x) usually mean the same thing in Python
- del deletes references, not objects. Python’s garbage collector may discard an object from memory as an indirect result of del, if the deleted variable was the last reference to the object. Rebinding a variable may also cause the number of references to an object to reach zero, causing its destruction
- In CPython, the primary algorithm for garbage collection is reference counting. Essentially, each object keeps count of how many references point to it. As soon as that refcount reaches zero, the object is immediately destroyed: CPython calls the __del__ method on the object (if defined) and then frees the memory allocated to the object. In CPython 2.0, a generational garbage collection algorithm was added to detect groups of objects involved in reference cycles—which may be unreachable even with outstanding references to them, when all the mutual references are contained within the group. Other implementations of Python have more sophisticated garbage collectors that do not rely on reference counting, which means the __del__ method may not be called immediately when there are no more references to the object
1 | a = [1, 2] |
References
- Fluent Python