0

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

kfsone
  • 23,617
  • 2
  • 42
  • 74
  • I've given an explanation below, but there's a good deal that's still unclear about why you're trying to do things this way. For one thing, why are you making your decorator a class rather than a function? – BrenBarn Nov 01 '14 at 01:04
  • @BrenBarn I'd spent a bunch of time reading about decorators and "use a class" seemed to be the encouraged way to do it, as well as other S/O questions (http://stackoverflow.com/questions/7492068/python-class-decorator-arguments).. – kfsone Nov 01 '14 at 02:31

2 Answers2

2

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.

BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • FWIW, beyond the scope of the question: I'm experimenting with various ways I can achieve self-registration of namespaces (not class instances, but class definitions). Basically I have a module directory with a __init__.py that defines "Base", and then to add a new command to the system you create newcmd.py which has "class NewCommand(Base)" and "NewCommand" shows up automatically in the parser, but only actually gets instantiated when you start the top-level engine and use the "NewCommand" command. – kfsone Nov 01 '14 at 02:37
  • 1
    @kfsone: If you want classes to automatically register themselves, look into metaclasses. – BrenBarn Nov 01 '14 at 02:38
  • most of the meta-class mechanisms I've seen require boiler-plate of some form (each derivation has to opt-in), I was hoping that decorators were implemented as some form of inherited attribute so that I could achieve this with a single decoration, but I'll go back to that avenue, ty :) – kfsone Nov 01 '14 at 02:39
  • 1
    @kfsone: I think you have it backwards. If you decorate a class, none of its subclass are thereby decorated, unless you manually decorate them too. But if a class has a certain metaclass, then all of its subclasses also have that metaclass, unless you manually give them a different one. So in general decorators are "opt-in" and metaclasses are "opt-out". There are plenty of questions about both decorators and metaclasses here on SO to look through. Then you can go ahead and ask another question if you still have doubts. – BrenBarn Nov 01 '14 at 02:45
0

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()
Paul Cornelius
  • 9,245
  • 1
  • 15
  • 24