2010-06-16 13 views
12

Desidero essere in grado di creare un decoratore python che "registri" automaticamente i metodi di classe in un repository globale (con alcune proprietà).Metodi di classe con registrazione automatica mediante decoratore

codice Esempio:

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

voglio che quando il caricamento è fatto, da qualche parte ci sarà un dict contenente:

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

è possibile?

risposta

15

Non solo con un decoratore, no. Ma una metaclasse può funzionare automaticamente con una classe dopo che è stata creata. Se il vostro register decoratore fa solo appunti su ciò che la metaclasse dovrebbe fare, è possibile effettuare le seguenti operazioni:

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 

stampa

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

+1 questa è la strada da percorrere (quando hai davvero bisogno di qualcosa di simile) –

+0

Cosa succede se hai una sottoclasse, OurClass (MyClass) - e vuoi fare la stessa cosa? Come si può ottenere __metaclass__ per attraversare gli attrs di base e di figlio? – jMyles

+0

@jMyles, i metodi della classe genitore sono già presenti nel registro; vuoi che si presentino di nuovo come metodi sulla classe bambino?Hai la base della tupla per la classe bambino; è possibile scorrere su uno o tutti i dizionari di classe delle classi genitore/base, nonché gli attributi della nuova classe figlio, quando si esegue la registrazione della classe figlio (se questo è ciò che si intende). –

1

No. Il decoratore riceve la funzione prima che diventi un metodo, quindi non si sa su che classe si trova.

+1

Ma puoi scrivere il tuo metaclasse (o decoratore di classe) per esaminare i metodi della classe e cercare metodi con un campo aggiuntivo '_register_properties', che è stato aggiunto prima dal decoratore' register'. Per favore dimmi, se vuoi un esempio su questo. – tux21b

2

Non è facile, ma se si sta utilizzando Python 3 questo dovrebbe funzionare:

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) 

Si noti che non è possibile avere i nomi dei metodi uguale al nome decoratore nella classe meta-digitato, poiché vengono automaticamente eliminati tramite il comando del nel metodo __new__ della metaclass.

Per Python 2.6 penso che dovresti dire esplicitamente al decoratore il nome della classe da utilizzare.

12

Ecco un po 'di amore per i decoratori di classe. Penso che la sintassi sia leggermente più semplice di quella richiesta per i metaclassi.

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

maghi se l'ho mai visto! – ThorSummoner

+0

Funziona se 'MyClass' è definito in un altro modulo? (supponendo che il modello importi 'class_register') – DBCerigo

2

Non come bello o elegante, ma probabilmente il modo più semplice se solo bisogno di questo in una sola classe:

_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

Se è necessario il nome di classi, utilizzare la soluzione di Matt. Tuttavia, se siete ok con solo avere il nome di metodi - o un riferimento al metodo - nel Registro di sistema, questo potrebbe essere un modo più semplice per farlo:

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 

stampa

{'my_other_method': ('prop3', 'prop4'), 'my_method': ('prop1', 'prop2')} 
Problemi correlati