In early Python versions (pre-2.2), methods were typed objects distinct from functions, requiring explicit type checks to handle bound versus unbound states. The introduction of new-style classes and the unified type/class model in Python 2.2 eliminated the method type as a separate entity for functions, shifting the binding responsibility to the descriptor protocol. This evolution allowed functions themselves to implement __get__, creating bound methods dynamically only when accessed through instances, thereby simplifying the language object model and reducing internal type complexity.
When a user defines a method inside a class, the underlying object stored in the class dictionary is a plain function expecting self as its first argument. The challenge lies in ensuring that when this attribute is retrieved via an instance (e.g., obj.method), Python transparently constructs a callable that automatically supplies that instance as the first positional argument without requiring manual partial application or wrapper code. This must occur efficiently on every attribute access while maintaining the ability to access the unbound function via the class (e.g., Class.method) for explicit self-passing or inheritance inspection.
Functions implement the descriptor protocol via their __get__ method. When accessed on a class (None instance), __get__ returns the function object itself. When accessed on an instance, __get__(self, instance, owner) returns a method object that encapsulates both the function and the instance. Upon invocation, this bound method prepends the instance to the argument tuple before calling the underlying function.
class Demo: def compute(self, value): return value * 2 d = Demo() # Class access returns the raw function unbound = Demo.__dict__['compute'] print(type(unbound)) # <class 'function'> # Instance access triggers __get__, returning a bound method bound = unbound.__get__(d, Demo) print(type(bound)) # <class 'method'> print(bound(5)) # 10, equivalent to d.compute(5)
Developing a high-frequency trading system requires strategy objects to register price update handlers with a market data feed. Initially, developers passed strategy.on_price_update as the callback reference. During load testing, memory profiling revealed that deleted strategies were not being garbage collected because the feed held bound method references, creating accidental strong reference cycles that persisted for the application's lifetime.
One approach involved storing weak references to the strategy and the unbound function separately, then manually combining them at invocation time. This prevents circular references and allows immediate garbage collection of abandoned strategies. However, this introduces complex callback invocation logic, potential race conditions if the object is collected between the liveness check and the call, and breaks Python's intuitive method passing idiom.
Another option converted on_price_update to a @staticmethod and passed the strategy instance explicitly during registration. This simplifies reference management by avoiding bound method creation entirely. Unfortunately, this violates object-oriented encapsulation principles, forces changes to the registration API to accept both function and instance separately, and produces less readable code that obscures the relationship between the strategy and its handler.
We considered implementing a custom descriptor returning a bound method-like object holding a weak reference to the instance instead of a strong one. This maintains the obj.method call syntax and prevents memory leaks while remaining idiomatic from the caller's perspective. The drawback is the requirement for deep descriptor protocol knowledge to implement correctly and the slight overhead of checking reference liveness on each call.
We selected Solution 3, implementing a WeakMethod descriptor that mimics standard function binding but uses weakref.ref for the instance. This allowed the market data feed to hold callbacks without preventing strategy garbage collection. The approach preserved clean registration code: feed.register(ticker, strategy.on_price_update).
This optimization eliminated memory leaks in long-running trading sessions and reduced memory footprint by 40% during backtesting with millions of transient strategy instances. The system maintained clean object-oriented API design without requiring users to understand reference management complexities. Ultimately, understanding the bound method creation mechanism proved essential for building production-grade financial software.
Why does storing a bound method in a long-lived container prevent garbage collection of the associated instance even after all original references disappear?
A bound method object maintains an internal __self__ attribute holding a strong reference to the instance. When stored in a global registry or cache, the method keeps the instance reachable indefinitely. To avoid this, developers must use weakref.WeakMethod or store unbound functions with separate weak instance references.
How does the @classmethod descriptor's __get__ implementation differ from standard functions to enable polymorphic factory methods?
classmethod is a non-data descriptor that binds the owner class to the first argument rather than the instance. When accessed on a subclass, it receives that subclass as cls, enabling alternative constructors that instantiate the correct derived type. This contrasts with static methods, which receive no automatic binding and cannot determine the calling class without explicit inspection.
What overhead occurs at the CPython level when repeatedly accessing instance methods in tight loops, and why does method caching improve performance?
Each access obj.method triggers the descriptor protocol, allocating a new PyMethodObject on the heap containing pointers to the function and instance. This repeated allocation and deallocation creates significant overhead in high-frequency loops. Caching the bound method outside the loop reuses the same object, eliminating descriptor lookup costs and reducing execution time by 20-30% in microbenchmarks.