Functions as Non-Data Descriptors
Swipe um das Menü anzuzeigen
Every Python function is a non-data descriptor. This is not an implementation detail – it is exactly how method binding works. When you access obj.method, Python does not find a pre-bound method stored somewhere. It calls function.__get__(obj, type(obj)) on the fly, which returns a bound method with the instance already attached.
Functions Define __get__
123456# Functions have __get__ – they are non-data descriptors def greet(self): return f"Hello from {self.__class__.__name__}" print(hasattr(greet, "__get__")) # True print(hasattr(greet, "__set__")) # False – non-data descriptor
How Method Binding Works
When you write obj.method(), Python's attribute lookup finds method on the class (not the instance), sees it is a descriptor, and calls method.__get__(obj, type(obj)). This returns a bound method – a callable that prepends obj as the first argument:
1234567891011121314151617181920# Observing method binding step by step class Analyst: def __init__(self, name): self.name = name def generate_report(self, period): return f"{self.name}: report for {period}" analyst = Analyst("Alice") # These are equivalent print(analyst.generate_report("Q1")) raw_function = Analyst.__dict__["generate_report"] bound_method = raw_function.__get__(analyst, Analyst) print(bound_method("Q1")) # Same result # The bound method carries the instance print(bound_method.__self__ is analyst) # True print(bound_method.__func__ is raw_function) # True
Why Functions Are Non-Data Descriptors
Because functions only define __get__ (not __set__), they are non-data descriptors. This means they can be shadowed by instance attributes with the same name:
123456789101112# Instance attribute shadows a method of the same name class Processor: def run(self): return "Running processor" processor = Processor() print(processor.run()) # "Running processor" – calls method # Shadowing the method with an instance attribute processor.run = lambda: "Overridden at instance level" print(processor.run()) # "Overridden at instance level" – instance wins print(Processor.run(processor)) # Still accessible via the class
This is by design – it allows per-instance method overriding without subclassing.
Lambda and Partial as Descriptors
Any callable defined with def or lambda that lives in a class body is a non-data descriptor. functools.partial objects are not descriptors – they do not define __get__, so they do not bind:
12345678910111213import functools # Comparing function binding vs partial (no binding) def regular_method(self, value): return f"{self.__class__.__name__}: {value}" class Report: method = regular_method # Binds on access partial_method = functools.partial(print, "prefix") # Does not bind report = Report() print(report.method("Q1")) # "Report: Q1" – self is bound print(report.partial_method("Q1")) # "prefix Q1" – no self bound
The Full Picture
Danke für Ihr Feedback!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen