Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Apprendre Functions as Non-Data Descriptors | Descriptors in the Wild
Python Descriptors Explained

Functions as Non-Data Descriptors

Glissez pour afficher le menu

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:

12345678910111213
import 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

question mark

Why can a function defined as a class attribute be shadowed by an instance attribute with the same name?

Sélectionnez la réponse correcte

Tout était clair ?

Comment pouvons-nous l'améliorer ?

Merci pour vos commentaires !

Section 2. Chapitre 4

Demandez à l'IA

expand

Demandez à l'IA

ChatGPT

Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion

Section 2. Chapitre 4
some-alt