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

Peter Otten __peter__ at web.de
Sat Mar 5 12:17:44 EST 2016


Antoon Pardon wrote:

> Op 05-03-16 om 16:18 schreef Chris Angelico:
>> On Sun, Mar 6, 2016 at 2:05 AM, Antoon Pardon
>> <antoon.pardon at rece.vub.ac.be> wrote:
>>> Using python 3.4/3.5
>>>
>>> Suppose I have the following class:
>>>
>>> class Tryout:
>>>
>>>     @extern
>>>     def method(self, ...)
>>>
>>> Now how can I have access to the Tryout class in
>>> the extern function when it is called with method
>>> as argument
>>>
>>> def extern(f):
>>>     the_class = ????
>>>
>>> f.__class doesn't work, if I write the following
>>>
>>> def extern(f)
>>>     print(f.__class__)
>>>
>>> the result is: <class 'function'>, so that doesn't work.
>>> Looking around I didn't find an other obvious candidate
>>> to try. Anybody an idea?
>> 
>> At the time when the function decorator is run, there isn't any class.
>> You could just as effectively create your function outside the class
>> and then inject it (Tryout.method = method).
>> 
>> What is it you're trying to do? Would it be a problem to have a class
>> decorator instead/as well?
>> 
>> ChrisA
>> 
> 
> 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
> would have been with the decorator extern, which would just have put
> the name of the method in the Tryout.allowed set and then return the
> function.

A simple alternative would be a flag

class Forbidden(Exception):
    pass

def extern(f):
    f.published = True
    return f

def invoke(obj, command):
    try:
        method = getattr(obj, command)
        if not method.published:
            raise Forbidden
    except (AttributeError, Forbidden):
        raise Forbidden("Forbidden command %r" % command)
    return method()

invoke(tryout, "foo")

Or if you need the set of allowed commands:

def make_extern():
    allowed = set()
    def extern(f):
        allowed.add(f.__name__)
    return allowed, extern

class Tryout:
    allowed, extern = make_extern()
    @extern
    def method(...): ...

Too much boilerplate? Hide it in a baseclass or metaclass:

class Meta(type):
    def __prepare__(*args, **kw):
        allowed = set()
        def extern(f):
            allowed.add(f.__name__)
        return dict(allowed=allowed, extern=extern)

class Tryout(metaclass=Meta):
    @extern
    def foo(self): ...
    @extern
    def bar(self): ...
    def baz(self): ...

assert Tryout.allowed == {"foo", "bar"}






More information about the Python-list mailing list