2010-06-16 17 views
12

Quiero ser capaz de crear un decorador de python que "registre" automáticamente los métodos de clase en un repositorio global (con algunas propiedades).Métodos de autoregistro de clase utilizando el decorador

código Ejemplo:

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... 

Quiero que cuando se realiza la carga, en algún lugar habrá un diccionario que contiene:

{ "my_class.my_method"  : (prop1, prop2) 
    "my_class.my_other_method" : (prop3, prop4) } 

Es esto posible?

Respuesta

15

No con solo un decorador, no. Pero una metaclase puede funcionar automáticamente con una clase después de su creación. Si su register decorador sólo hace notas sobre lo que debe hacer la metaclase, puede hacer lo siguiente:

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 

impresión

{'MyClass.my_other_method': ('prop3', 'prop4'), 'MyClass.my_method': ('prop1', 'prop2')} 
+0

+1 este es el camino a seguir (cuando realmente necesita algo como esto) –

+0

¿Qué pasa si tiene una subclase, OurClass (MyClass) - y desea hacer lo mismo? ¿Cómo se puede obtener __metaclass__ para atravesar los atributos de base y de niño? – jMyles

+0

@jMyles, los métodos de la clase principal ya están en el registro; ¿Quieres que aparezcan nuevamente como métodos en la clase infantil?Tienes las bases tuplas para la clase infantil; puede iterar sobre cualquiera o todos los diccionarios de clase de las clases principales/base, así como los atributos de la nueva clase secundaria, al realizar el registro de la clase secundaria (si eso es lo que quiere decir). –

1

No. El decorador recibe la función antes de convertirse en un método, por lo que no sabe en qué clase está.

+1

Pero puede escribir su propia metaclass (o decorador de clases) para examinar los métodos de la clase y buscar métodos con un campo '_register_properties' adicional, que fue agregado por el decorador' register' antes. Por favor dime, si quieres un ejemplo sobre eso. – tux21b

2
No

fácil, pero si usted está utilizando Python 3 esto debería funcionar:

registry = {} 
class MetaRegistry(type): 
    @classmethod 
    def __prepare__(mcl, name, bases): 
     def register(*props): 
      def deco(f): 
       registry[name + "." + f.__name__] = props 
       return f 
      return deco 
     d = dict() 
     d['register'] = register 
     return d 
    def __new__(mcl, name, bases, dct): 
     del dct['register'] 
     cls = super().__new__(mcl, name, bases, dct) 
     return cls 

class my_class(object, metaclass=MetaRegistry): 
    @register('prop1','prop2') 
    def my_method(arg1,arg2): 
     pass # method code here... 

    @register('prop3','prop4') 
    def my_other_method(arg1,arg2): 
     pass # method code here... 

print(registry) 

Tenga en cuenta que no se puede tener nombres de método igual al nombre de decorador en la clase de meta-escrito, ya que se eliminan automáticamente por el comando del en el método __new__ de la metaclase.

Para Python 2.6 Creo que tendrías que decirle explícitamente al decorador el nombre de la clase a usar.

12

Aquí hay un poco de amor para decoradores de clase. Creo que la sintaxis es un poco más simple que la requerida para las metaclases.

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

hechicería si alguna vez lo he visto! – ThorSummoner

+0

¿Funciona esto si 'MyClass' está definido en otro módulo? (suponiendo que el modelo importe 'class_register') – DBCerigo

2

No es tan bonito o elegante, pero probablemente la forma más sencilla si sólo necesita esto en una sola clase:

_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 
5

Si necesita el nombre de clases, utilizar la solución de Matt. Sin embargo, si estás bien con sólo tener el nombre de métodos - o una referencia al método - en el registro, esto podría ser una manera más sencilla de hacerlo:

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 

de impresión

{'my_other_method': ('prop3', 'prop4'), 'my_method': ('prop1', 'prop2')} 
Cuestiones relacionadas