Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
329: Weak References with the weakref Module
Imagine you're managing a massive warehouse. Most of your inventory is held in place by heavy-duty steel chains (strong references). As long as a chain is attached to a crate, that crate stays in the warehouse. If you have ten different chains attached to one crate, it doesn't matter if you decide you don't need nine of them—as long as one chain remains, the crate stays. This is how Python's memory management generally works: if something points to an object, that object lives.
Now, imagine you want a directory that tells you where certain crates are, but you don't want the directory itself to be the reason a crate stays in the warehouse. Instead of a chain, you use a sticky note (a weak reference). The sticky note says, "The blue crate is in Aisle 4." If the janitor comes through and sees that every single steel chain has been removed from the blue crate, they'll throw it away—even if your sticky note is still there. When you go to check your note and head to Aisle 4, you'll simply find the crate is gone.
When Strong References Become a Burden
In Python, we usually love strong references because they prevent our data from disappearing mid-execution. But this becomes a problem when you're building things like caches or registries. If your cache holds a strong reference to every object it has ever seen, those objects can never be garbage collected, leading to a slow, agonizing memory leak.
import weakref
class LargeResource:
def __init__(self, name):
self.name = name
print(f"Loading {self.name} into memory...")
def __del__(self):
print(f"Cleaning up {self.name}!")
# A strong reference
res = LargeResource("HeavyAsset_01")
# A weak reference
res_weak = weakref.ref(res)
print(f"Weak ref points to: {res_weak()}") # Returns the object
del res # We remove the only strong reference
print(f"Weak ref points to: {res_weak()}") # Returns None
Notice how res_weak is called like a function? That's because a weak reference isn't the object itself; it's a handle. When you call it, Python checks if the object still exists. If it does, you get the object back. If the garbage collector already reclaimed it, you get None. I've found this pattern invaluable when I need to "keep an eye" on an object without accidentally extending its lifespan.
Solving the Circular Reference Trap
One of the most annoying bugs I've encountered in large systems is the circular reference. Imagine a Parent object that owns a Child, and the Child needs a reference back to its Parent. If both use strong references, they hold each other up in a "death grip," and neither can be collected unless the garbage collector does a full sweep of unreachable cycles, which is more expensive than simple reference counting.
By making the back-reference weak, you break the cycle:
class Parent:
def __init__(self, name):
self.name = name
self.child = None
class Child:
def __init__(self, name, parent):
self.name = name
# Use a weakref to avoid the circular death grip
self.parent = weakref.ref(parent)
p = Parent("Owner")
c = Child("Dependent", p)
p.child = c
# If we delete p, c's reference to p doesn't keep p alive.
del p
print(c.parent()) # Now returns None
Cleaning up Syntax with Weakref Proxies
Calling res_weak() every time is a bit clunky. If you hate the parentheses, Python provides weakref.proxy. A proxy behaves exactly like the original object—you can access attributes and call methods directly—but it doesn't increase the reference count. The catch? If the object is collected, the proxy doesn't return None; it raises a ReferenceError. It's a trade-off between cleaner code and more explicit error handling.
res = LargeResource("HeavyAsset_02")
proxy = weakref.proxy(res)
print(proxy.name) # Works exactly like a normal object
del res
# print(proxy.name) # This would raise ReferenceError
I generally prefer weakref.ref when I expect the object to disappear and want to handle it gracefully with an if statement. Use proxy when the object *should* be there for the duration of the operation, and a disappearance would actually be an exceptional error.
📋 Practical Task
Build a Memory-Efficient Asset Registry
You are building a game engine where Texture objects are expensive to load. You need a TextureRegistry that tracks all currently active textures so you don't load the same file twice. However, the registry must not prevent textures from being deleted when the rest of the game is finished with them.
Implement the following:
- A
Textureclass that prints "Texture [name] created" on__init__and "Texture [name] destroyed" on__del__. - A
TextureRegistryclass with aget_texture(name)method. - The
get_texturemethod should:- Check if the texture is already in the registry and still alive.
- If it exists, return it.
- If it doesn't exist (or was garbage collected), create a new
Texture, store a weak reference to it in the registry, and return the texture.
Test your implementation: Create a texture via the registry, delete your local reference to it, and verify that the registry no longer holds a strong reference (the "destroyed" message should print immediately).
There are no comments for now.