`__set_name__` hook manually added to `functools.wraps()` descriptor instance never called

Question:

I’m trying to add a __set_name__ hook to the descriptor produced by functools.wraps inside a decorator, but it is never called and I don’t see any error messages:

import functools


def wrap(fn):
    """Decorator."""

    @functools.wraps(fn)
    def w(*args, **kwargs):
        return fn(*args, **kwargs)

    # This never gets called.
    def set_name(self, obj, name):
        print(f"inside __set_name__: {self=}, {obj=}, {name=}")

    w.__set_name__ = set_name.__get__(w)
    return w


class Foo:
    @wrap
    def foo(self):
        pass

From what I understand, wrap() is called and its return value bound to the foo variable in the class’s execution frame before the Foo class is created, so the __set_name__ hook should be in place by the time Python looks for it. So why isn’t it being called?

Asked By: Richard Hansen

||

Answers:

Whenever Python looks for magic methods, it looks on the type of the object, not the instance. What you’ve done is take a function object (the return value of functools.wrap, in this case) and assign something on its __dict__. But for efficiency (and correctness, in some cases), special methods like __set_name__ bypass __dict__ and look on the type object directly. See Special method lookup for details and a rationale.

To make your code work, you need to create a custom callable class (i.e. a class which defines a function called __call__), define __set_name__ on that class, and then make w an instance of that class.

Answered By: Silvio Mayolo