简体   繁体   中英

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).

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.

To amplify what BrenBarn said in his answer, I modified your code. "tagged" is now a function that returns a class. As you can verify, this code runs without error (confirmed with python 3.3). The output indicates that the name "Base" is now bound to a class object and as such can be inherited by Derived. But the string "Derived class" never prints.

I'm not sure this can be used to accomplish what you want, but it might help to clarify how class decorators work.

def tagged(regClass):
    """ Decorator itself. """
    class tagme:   
        def __init__(self):
            """ called after class definition """
            print(self)
            print(regClass)
            print(regClass.name)
    return tagme

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

print("After defining base",Base)
b = Base()

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

d = Derived()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM