Dan P.
Dan P.

Reputation: 1431

How do you use a class attribute on a class instance method decorator?

I have some classes that look something like this:

class Foo:
    bar = None

    @baz(frob=bar)
    def oof():
        pass

class Rab(Foo):
    bar = 'zab'

I don't have control over the @baz decorator and I need to to use the class attribute bar of the new class Rab. But calling Rab.oof() uses the bar=None of the base class.

Upvotes: 8

Views: 237

Answers (3)

Andrew Clark
Andrew Clark

Reputation: 208465

So here is a kind of weird/interesting approach where you only need to modify Foo, the idea here is that a new decorator is created that delays the baz decoration until the function is first called with some caching based on the class name so that it only happens once.

Note that this also includes a dummy implementation for baz which just prints the frob argument that was provided, but this approach should work fine without needing to modify baz:

def baz(frob):
    def deco(func):
        def wrapped(*args, **kwargs):
            print('frob:', frob)
            return func(*args, **kwargs)
        return wrapped
    return deco

def newdeco(func):
    def wrapped(self, *args, **kwargs):
        if not hasattr(wrapped, 'cache'):
            wrapped.cache = {}
        cls = self.__class__.__name__
        if cls not in wrapped.cache:
            wrapped.cache[cls] = baz(frob=getattr(self.__class__, 'bar'))(func)
        wrapped.cache[cls](self, *args, **kwargs)
    return wrapped

class Foo:
    bar = None

    @newdeco
    def oof(self):
        pass

class Rab(Foo):
    bar = 'zab'


f = Foo()
f.oof()

r = Rab()
r.oof()

I also had to add a self argument to oof based on the assumption that oof is a method, if baz is also converting the function to a staticmethod I'm not sure this approach will work.

Upvotes: 5

shx2
shx2

Reputation: 64318

You can keep the undecorated version of oof, let's call it _oof_raw, and re-wrap it in your subclass definition. In order to keep the undecorated version, you need to decorate "manually", i.e. not using the @ syntactic sugar.

class Foo:
    bar = None

    def _oof_raw(self):
        pass

    oof = baz(frob=bar)(_oof_raw)

class Rab(Foo):
    bar = 'zab'

    oof = baz(frob=bar)(Foo._oof_raw)

Upvotes: 2

Silas Ray
Silas Ray

Reputation: 26150

You could unwrap the closure from Foo and pull out the wrapped function, then re-wrap it in the decorator in the derived class. But if there's any way you can get at Foo or even baz to change their implementation, it'd be better.

class Rab(Foo):
    bar = 'zab'
    oof = baz(frob=bar)(Foo.oof.func_closure[0].cell_contents)

Upvotes: 3

Related Questions