How do I give a decorator acces to the class of a decorated function

Peter Otten __peter__ at web.de
Thu Sep 5 09:30:56 EDT 2019


Antoon Pardon wrote:

> On 4/09/19 17:46, Peter Otten wrote:
>> Antoon Pardon wrote:
>>
>>> What I am trying to do is the following.
>>>
>>> class MyClass (...) :
>>>     @register
>>>     def MyFunction(...)
>>>         ...
>>>
>>> What I would want is for the register decorator to somehow create/mutate
>>> class variable(s) of MyClass.
>>>
>>> Is that possible or do I have to rethink my approach?
>> If you are willing to delegate the actual work to the metaclass call:
>>
>> def register(f):
>>     f.registered = True
>>     return f
>>
>> def registered(name, bases, namespace):
>>     namespace["my_cool_functions"] = [
>>         n for n, v in namespace.items()
>>         if getattr(v, "registered", False)
>>     ]
>>     return type(name, bases, namespace)
>>
>> class MyClass(metaclass=registered) :
>>     @register
>>     def foo(self):
>>         pass
>>     @register
>>     def bar(self):
>>         pass
>>     def other(self):
>>         pass
>>
>> print(MyClass.my_cool_functions)
> 
> I have been playing with this idea and it looks promising. I was wondering
> about two points.
> 
> 1) I guess I can add extra methods to my class through the metaclass by
>    having something like the following in the registered function:
> 
> def registered(name, bases, namespace):
>     namespace["my_cool_functions"] = [
>         n for n, v in namespace.items()
>         if getattr(v, "registered", False)
>     ]
>     namespace["__getitem__"] = lambda self, index:
>     self.my_cool_functions[index]

Methods are just functions as class attributes, so yes. Problems may arise 
with __private attributes and super().

> 
> 2) Is it possible to make MyClass automatically a subclass of an other
> class
>    through the metaclass?
> 

While you can modify `bases` before passing it on to `type` this starts to 
get a bit messy. Maybe you need a real metaclass which unlike the registered 
function above is shared by the subclasses...

import random

def register(f):
    f.registered = True
    return f

class Foo: pass
class Bar: pass

class RegisterMeta(type):
    def __new__(cls, name, bases, namespace):
        namespace["my_cool_functions"] = [
            n for n, v in namespace.items()
            if getattr(v, "registered", False)
        ]
        return type.__new__(
            cls, name,
            bases + (random.choice([Foo, Bar]),),
            namespace
        )

class RegisterBase(metaclass=RegisterMeta):
    def __getitem__(self, i):
        return self.my_cool_functions[i]

class MyClass(RegisterBase):
    @register
    def foo(self):
        pass
    @register
    def bar(self):
        pass
    def other(self):
        pass

print(MyClass.my_cool_functions)
print(MyClass()[0])
print(MyClass.my_cool_functions is RegisterBase.my_cool_functions)  # False
print(MyClass.__bases__, RegisterBase.__bases__)


...or something else entirely. Can you provide some context?




More information about the Python-list mailing list