Search code examples
pythonpython-3.xpython-decorators

Python class decorator doesn't have the module/name I expect


How can I apply a "subclassing pattern", but have the class "show up" (as in module/name) as coming with the name I gave it, from the module I made it and not from the module I made the maker.

No doubt, and example is warranted here. Say I have a modules:

# module_with_wrapper.py
from functools import wraps

def my_wrap(cls, name=None):
    assert isinstance(cls, type)
    class WrappedClass(cls):
        @wraps(cls.__init__)
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.something_else = 42
    WrappedClass.__name__ = name or f"{cls.__name__}Wrapped"
    return WrappedClass

used in the following module:

# a_module.py
from functools import wraps

class A:
    def __init__(self, a):
        self.a = a

class B(A):
    @wraps(A.__init__)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.something_else = 42

from module_with_wrapper import my_wrap

BB = my_wrap(A, 'BB')  # define class BB, expected to be equivalent to B

The intent is to be able to reuse the subclassing pattern exampled by B, anywhere I want, with any class I want.

And it works, but the module/name is not what I expect. I expect my BB to show up as a_module.BB, not from module_with_wrapper.my_wrap.<locals>.WrappedClass!

>>> from a_module import B, BB
>>>
>>> B
<class 'a_module.B'>
>>> b = B(a=0)
>>> b
<a_module.B object at 0x10e40eb50>
>>>
>>> BB  # what the?! I thought I told it it's name was BB!!
<class 'module_with_wrapper.my_wrap.<locals>.WrappedClass'>
>>> bb = BB(a=0)
>>> bb
<module_with_wrapper.my_wrap.<locals>.WrappedClass object at 0x10e40ebd0>
>>> BB.__name__  # okay, at least it got THAT right!
'BB'

Solution

  • While repr used the __name__ attribute in python 2, in python 3 (>=3.3) it uses the new __qualname__ attribute to provide more context. If you override __qualname__ in your decorator, you should get the desired result.

    See this question for further information on the __qualname__ attribute.