Search code examples
pythonpython-3.xdecoratorpython-decorators

Python decorator class on namespaces with inheritance


Trying to use decorator-classes on a base class in python 3 but not fully understanding the behavior I observe.

class tagged:
    """ Decorator itself. """
    def __init__(self, regClass, *args, **kwargs):
        """ called after class definition """
        print(self, regClass, regClass.name)

@tagged
class Base(object):
    name = "Base class"

#class Derived(Base):
#    name = "Derived class"

The first class works as expected, and I see

__init__ <__main__.tagged object at 0x100632cc0> <class '__main__.Base'>
Base class

But when I uncomment Derived it is not forwarding its arguments to the decorator the way I expected.

_init__ <__main__.tagged object at 0xb74c516c> <class '__main__.Base'>
Base class
__init__ <__main__.tagged object at 0xb74c51cc> Derived
Traceback (most recent call last):
  File "./prog.py", line 10, in <module>
  File "./prog.py", line 4, in __init__
AttributeError: 'str' object has no attribute 'name'

My motivation here is improving my Pyfu, in particular I'm trying out various ways of achieving self-registration in sub-modules (specifically, these modules are sub-commands which register themselves with a sub-command index and then supply their argument sets to the parser if and only if the specific sub-command is selected).


Solution

  • You have a few problems here. The main one is that your tagged decorator, when used to decorate a class, returns an instance of tagged, not a class. That's how decorators work: your class Base definition is replaced with the result of calling tagged(A). A decorator is not just "called after class definition" --- the result of the decorator call replaces the original class. So your decorated class winds up not being a class at all, but an instance (of tagged).

    As a result, the metaclass of Derived winds up being tagged (because that is the type of its base "class" Base, which as mentioned is actually an instance of tagged), so defining Derived tries to call tagged.__init__, and fails because the argument it gets passed for regClass is the name of the class ("Derived") instead of the class. (I'm guessing from your question that the details of why you're getting this particular error are probably not relevant for what you're trying to do, though.)

    It's not really clear what you're trying to do here. You mention "forwarding its arguments to the decorator", but class decorators don't work that way. When you decorate a class, it doesn't automatically decorate its subclasses too.