2010-06-16 2 views
12

Я хочу, чтобы иметь возможность создать декоратор python, который автоматически «регистрирует» методы класса в глобальном репозитории (с некоторыми свойствами).Методы класса авторекламы с использованием декоратора

Пример кода:

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

Я хочу, чтобы при загрузке не будет сделано, где-то там будет Dict, содержащий:

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

Возможно ли это?

ответ

15

Не только с декоратором, нет. Но метакласс может автоматически работать с классом после его создания. Если register декоратор просто делает заметки о том, что метаклассом должен сделать, вы можете сделать следующее:

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 

печати

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

+1 Это способ пойти (когда вам действительно нужно что-то подобное) –

+0

Что делать, если у вас есть подкласс OurClass (MyClass) - и вы хотите сделать то же самое? Как вы можете получить __metaclass__, чтобы пересечь attrs как базы, так и ребенка? – jMyles

+0

@jMyles, методы родительского класса уже находятся в реестре; вы хотите, чтобы они снова отображались как методы для дочернего класса?У вас есть кортеж для дочернего класса; вы можете перебирать любые или все словари классов родительского/базового классов, а также атрибуты нового дочернего класса при регистрации дочернего класса (если это то, что вы имеете в виду). –

1

№ Декоратор получает функцию до того, как она стала методом, поэтому вы не знаете, на каком классе она включена.

+1

Но вы можете написать свой собственный метакласс (или декоратор класса), чтобы просмотреть методы класса и найти методы с дополнительным полем '_register_properties', которое ранее было добавлено декоратором' register'. Скажите, пожалуйста, если вам нужен пример. – tux21b

2

Не легко, но если вы используете Python 3, это должно работать:

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) 

Обратите внимание, что вы не можете иметь имена методов, равное имени декоратора в мета-набран класс, потому что они автоматически удаляются командой del в методе metaclass __new__.

Для Python 2.6 Я думаю, вам нужно было бы явно указать декоратору имя класса для использования.

12

Вот небольшой любовь класса декораторов. Я думаю, что синтаксис несколько проще, чем для метаклассов.

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

волшебство, если я его когда-либо видел! – ThorSummoner

+0

Это работает, если 'MyClass' определен в другом модуле? (при условии, что модель импортирует 'class_register') – DBCerigo

2

Не так красиво и элегантно, но, вероятно, самый простой способ, если только это нужно только в одном классе:

_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

Если вам нужно имя классы, использовать решение Мэтта. Тем не менее, если вы нормально с просто имея название методов - или ссылку на метод - в реестре, это может быть более простой способ сделать это:

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')}