11

how does one go about accessing a decorator from a base class in a child?

I assumed (wrongly) that the ffg. would work:

class baseclass(object):
    def __init__(self):
        print 'hey this is the base'

    def _deco(func):
        def wrapper(*arg):
            res = func(*arg)
            print 'I\'m a decorator. This is fabulous, but that colour, so last season sweetiedarling'
            return res
        return wrapper

    @_deco
    def basefunc(self):
        print 'I\'m a base function'

This class works fine, but then I create a child class inheriting from this:

class otherclass(baseclass):
    def __init__(self):
        super(otherclass, self).__init__()
        print 'other class'


    @_deco
    def meh(self):
        print 'I\'m a function'

This won't even import properly, let alone run. @_deco is undefined. Trying baseclass._deco throws an unbound method _deco() error, which isn't really surprising.

Any idea how to do this, I'd really like to encapsulate the decorator in the class, but I'm not married to the idea and I'd need to call it in the base & the child class.

dochead
  • 1,785
  • 2
  • 18
  • 20
  • The use case for this is a rough timing decorator for a pylons controller. So it needs to be accessible from the baseclass because there are functions in there that'll have to be timed, and functions in the child class that need to be timed. I guess the simplest way would be to just rip it out of the class. – dochead Aug 06 '10 at 06:43
  • Could you correct your formatting? Your class's `def __init__` are at the same indentation as the class definition. – MattH Aug 06 '10 at 06:45

2 Answers2

15
class baseclass(object):
    def __init__(self):
        print 'hey this is the base'

    def _deco(func):
        def wrapper(*arg):
            res = func(*arg)
            print 'I\'m a decorator. This is fabulous, but that colour, so last season sweetiedarling'
            return res
        return wrapper

    @_deco
    def basefunc(self):
        print 'I\'m a base function'

    @_deco
    def basefunc2(self):
        print "I'm another base function"

   #no more uses of _deco in this class
    _deco = staticmethod(_deco) 
   # this is the key. it must be executed after all of the uses of _deco in 
   # the base class. this way _deco is some sort weird internal function that 
   # can be called from within the class namespace while said namespace is being 
   # created and a proper static method for subclasses or external callers.


class otherclass(baseclass):
    def __init__(self):
        super(otherclass, self).__init__()
        print 'other class'


    @baseclass._deco
    def meh(self):
        print 'I\'m a function'
aaronasterling
  • 68,820
  • 20
  • 127
  • 125
  • 1
    Fabulous, I'll have some of that, that is just so in this season, sweetiedarling. (I thought I'd just go with the theme.) – dochead Aug 08 '10 at 13:13
  • I also found that you can also use @staticmethod decorator, but ONLY if your not declaring @_deco in the base class (which you are). So this solution seems to better fit the question being asked. – jersey bean Feb 02 '18 at 00:30
  • Be careful with double underscores (like `__deco`), this will lead to some naming tricks. – Oleg Yablokov Jan 18 '22 at 11:30
1

There is also python3-specific way to use that decorator in child class without mentioning parent, exactly as OP suggested. It requires decorator to be implemented in parent's metaclass (nice explanation of metaclases can be found here), using its __prepare__() method.

aaronasterling's answer is valid and preferred way how to solve that, I am posting this only as an interesting example to help others understand the basics of language. Use metaclasses only when there is no other way to achive what you need!

class metaclass(type):
    @classmethod
    def __prepare__(metacls, name, bases):
        def _deco(func):
            def wrapper(*arg):
                res = func(*arg)
                print('I\'m a decorator. This is fabulous, but that colour, so last season sweetiedarling')
                return res
            return wrapper
        return {"_deco": _deco}

class baseclass(metaclass=metaclass):
    def __init__(self):
        print('hey this is the base')

    @_deco
    def basefunc(self):
        print('I\'m a base function')
        
class otherclass(baseclass):
    def __init__(self):
        super(otherclass, self).__init__()
        print('other class')

    @_deco
    def meh(self):
        print('I\'m a function')

The sample code works well in python3:

>>> obj = otherclass()
hey this is the base
other class
>>> obj.meh()
I'm a function
I'm a decorator. This is fabulous, but that colour, so last season sweetiedarling

Important notes about __prepare__() method:

  • If present, it runs before the object body is executed
  • Its return value is used as local namespace for the class body at the begining of its evaluation (this way, decorator can be availabe from child's body without using parent's namespace)
  • It should be implemented as classmethod() and should return mapping object (i.e. dict)
  • If not present, empty mapping is used as initial local namespace.
Michal
  • 90
  • 1
  • 2
  • 5