Can I find the class of a method in a decorator.

Steven D'Aprano steve at pearwood.info
Sat Mar 5 20:57:56 EST 2016


On Sun, 6 Mar 2016 03:21 am, Antoon Pardon wrote:

> The idea is that some of these methods will be externally available
> and others are not. So that I get an external string and can do
> something of the following:
> 
> tryout = Tryout()
> 
> st = read_next_cmd()
> 
> if st in tryout.allowed:
>     getattr(tryout, st)()
> else:
>     raise ValueError("%s: unallowed cmd string" % st)
> 
> And the way I wanted to populate Tryout.allowed as a class attribute

The simplest way to do this is to forget about the decorator, since it's the
wrong tool for the job. The decorator doesn't have access to the class
itself, unless you pass it as an argument to the decorator. But you can't
do that *inside* the class, since it doesn't exist yet.

Instead, just keep a separate set of the names of methods. Do the simplest
thing which could possibly work, that is, maintain the set manually:

(All code following is untested.)


class Tryout:
    allowed = set(['spam', 'ham', 'cheese'])

    def spam(self): ...
    def ham(self): ...
    def eggs(self): ...
    def cheese(self): ...



That suggests a way to avoid having to manually add the method name to the
set: have a decorator that adds it to a known set.


def external(aset):
    def decorator(func):
        aset.add(func.__name__)
        return func
    return decorator


class Tryout:
    allowed = set()

    @external(allowed)
    def spam(self): ...

    @external(allowed)
    def ham(self): ...

    def eggs(self): ...

    @external(allowed)
    def cheese(self): ...




Which leads us to our next improvement:

class Tryout:
    allowed = set()
    allow = functools.partial(external, allowed)

    @allow
    def spam(self): ...

    @allow
    def ham(self): ...

    def eggs(self): ...

    @allow
    def cheese(self): ...

    del allow




But there's another approach. A decorator cannot easily know anything about
the class that the method will belong to, but it can tag the method itself:


def allow(func):
    func.allowed = True
    return func


class Tryout:

    @allow
    def spam(self): ...

    @allow
    def ham(self): ...

    def eggs(self): ...

    @allow
    def cheese(self): ...



tryout = Tryout()
st = read_next_cmd()
method = getattr(tryout, st)
if method.allowed:
    method()
else:
    raise ValueError("%s: unallowed cmd string" % st)


Obviously you don't write that out in full each time, you factor it into a
method or function.




-- 
Steven




More information about the Python-list mailing list