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
Functions as Non-Data Descriptors
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!