Auto-register class methods using decorator

前端 未结 7 913
粉色の甜心
粉色の甜心 2020-12-05 00:46

I want to be able to create a python decorator that automatically \"registers\" class methods in a global repository (with some properties).

Example code:

         


        
相关标签:
7条回答
  • 2020-12-05 01:16

    Here's a little love for class decorators. I think the syntax is slightly simpler than that required for metaclasses.

    def class_register(cls):
        cls._propdict = {}
        for methodname in dir(cls):
            method = getattr(cls, methodname)
            if hasattr(method, '_prop'):
                cls._propdict.update(
                    {cls.__name__ + '.' + methodname: method._prop})
        return cls
    
    
    def register(*args):
        def wrapper(func):
            func._prop = args
            return func
        return wrapper
    
    
    @class_register
    class MyClass(object):
    
        @register('prop1', 'prop2')
        def my_method(self, arg1, arg2):
            pass
    
        @register('prop3', 'prop4')
        def my_other_method(self, arg1, arg2):
            pass
    
    myclass = MyClass()
    print(myclass._propdict)
    # {'MyClass.my_other_method': ('prop3', 'prop4'), 'MyClass.my_method': ('prop1', 'prop2')}
    
    0 讨论(0)
  • 2020-12-05 01:25

    To summarize, update and explain the existing answers, you have two options:

    1. Using Class Decorators (suggested by @unutbu)
    2. Using a Metaclass (suggested by @Matt Anderson)

    However, both of them rely on giving the function an attribute so it can be identified:

    def register(*args):
        """
        Creates an attribute on the method, so it can
        be discovered by the metaclass
        """
    
        def decorator(f):
            f._register = args
            return f
    
        return decorator
    

    1. The Class Decorator Approach

    import inspect
    
    def class_register(cls):
        for method_name, _ in inspect.getmembers(cls):
            method = getattr(cls, method_name)
            if hasattr(method, "_prop"):
                cls._propdict.update({f"{cls.__name__}.{method_name}": method._prop})
        return cls
    
    
    @class_register
    class MyClass:
    
        _propdict = {}
    
        @register("prop1", "prop2")
        def my_method(self, arg1, arg2):
            pass
    
        @register("prop3", "prop4")
        def my_other_method(self, arg1, arg2):
            pass
    
    
    print(MyClass._propdict)
    
    

    2. The Metaclass Approach

    registry = {}
    
    
    class RegisteringType(type):
        def __init__(cls, name, bases, attrs):
            for key, val in attrs.items():
                properties = getattr(val, "_register", None)
                if properties is not None:
                    registry[f"{name}.{key}"] = properties
    
    
    class MyClass(metaclass=RegisteringType):
        @register("prop1", "prop2")
        def my_method(self, arg1, arg2):
            pass
    
        @register("prop3", "prop4")
        def my_other_method(self, arg1, arg2):
            pass
    
    
    print(registry)
    
    0 讨论(0)
  • 2020-12-05 01:28

    If you need the classes name, use Matt's solution. However, if you're ok with just having the methods name -- or a reference to the method -- in the registry, this might be a simpler way of doing it:

    class Registry:
        r = {}
    
        @classmethod
        def register(cls, *args):
            def decorator(fn):
                cls.r[fn.__name__] = args
                return fn
            return decorator
    
    class MyClass(object):
    
        @Registry.register("prop1","prop2")
        def my_method( arg1,arg2 ):
            pass
    
        @Registry.register("prop3","prop4")
        def my_other_method( arg1,arg2 ):
            pass
    
    print Registry.r
    

    print

    {'my_other_method': ('prop3', 'prop4'), 'my_method': ('prop1', 'prop2')}
    
    0 讨论(0)
  • 2020-12-05 01:30

    Not with just a decorator, no. But a metaclass can automatically work with a class after its been created. If your register decorator just makes notes about what the metaclass should do, you can do the following:

    registry = {}
    
    class RegisteringType(type):
        def __init__(cls, name, bases, attrs):
            for key, val in attrs.iteritems():
                properties = getattr(val, 'register', None)
                if properties is not None:
                    registry['%s.%s' % (name, key)] = properties
    
    def register(*args):
        def decorator(f):
            f.register = tuple(args)
            return f
        return decorator
    
    class MyClass(object):
        __metaclass__ = RegisteringType
        @register('prop1','prop2')
        def my_method( arg1,arg2 ):
            pass
    
        @register('prop3','prop4')
        def my_other_method( arg1,arg2 ):
            pass
    
    print registry
    

    printing

    {'MyClass.my_other_method': ('prop3', 'prop4'), 'MyClass.my_method': ('prop1', 'prop2')}
    
    0 讨论(0)
  • 2020-12-05 01:32

    No. The decorator receives the function before it has become a method, so you don't know what class it is on.

    0 讨论(0)
  • 2020-12-05 01:33

    Not as beautiful or elegant, but probably the simplest way if you only need this in one class only:

    _registry = {}
    class MyClass(object):
        def register(*prop):
            def decorator(meth):
                _registry[MyClass.__name__ + '.' + meth.__name__] = prop
            return decorator
    
        @register('prop1', 'prop2')
        def my_method(self, arg1, arg2):
            pass
        @register('prop3', 'prop4')
        def my_other_method(self, arg1, arg2):
            pass
    
        del register
    
    0 讨论(0)
提交回复
热议问题