Auto-register class methods using decorator
Solution 1
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')}
Solution 2
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')}
Solution 3
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
{'my_other_method': ('prop3', 'prop4'), 'my_method': ('prop1', 'prop2')}
Solution 4
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
Solution 5
To summarize, update and explain the existing answers, you have two options:
- Using Class Decorators (suggested by @unutbu)
- 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)
Comments
-
adamk about 4 years
I want to be able to create a python decorator that automatically "registers" class methods in a global repository (with some properties).
Example code:
class my_class(object): @register(prop1,prop2) def my_method( arg1,arg2 ): # method code here... @register(prop3,prop4) def my_other_method( arg1,arg2 ): # method code here...
I want that when loading is done, somewhere there will be a dict containing:
{ "my_class.my_method" : ( prop1, prop2 ) "my_class.my_other_method" : ( prop3, prop4 ) }
Is this possible?
-
Fabio Suarez about 14 yearsBut you can write your own metaclass (or class decorator) to look through the methods of the class and search for methods with an additional
_register_properties
field, which was added by theregister
decorator before. Please tell me, if you want a example on that. -
jMyles about 9 yearsWhat if you have a subclass, OurClass(MyClass) - and you want to do the same thing? How can you get the metaclass to traverse the attrs of both base and child?
-
Matt Anderson about 9 years@jMyles, The parent class's methods are already in the registry; you want them to show up again as methods on the child class? You have the bases tuple for the child class; you can iterate over any or all of the class dictionaries of the parent / base classes, as well as the attributes of the new child class, when doing the child class's registration (if that's what you mean).
-
DBCerigo over 6 yearsDoes this work if
MyClass
is defined in another module? (assuming that model importsclass_register
) -
Uduse almost 5 yearsFor those who're using python3, you need
class MyClass(object, metaclass= RegisteringType):
instead -
Erikw over 2 yearsThanks for the summary! Method 2 works for me but Method 1 does not register any methods (using exact copy of code above. I'm using Python 3.9.