Как украсить класс?
В Python 2.5 есть ли способ создать декоратор, который украшает класс? В частности, я хочу использовать декоратор, чтобы добавить член в класс и изменить конструктор, чтобы принять значение для этого члена.
Ищем что-то вроде следующего (с синтаксической ошибкой в 'class Foo:':
def getId(self): return self.__id
class addID(original_class):
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
original_class.__init__(self, *args, **kws)
@addID
class Foo:
def __init__(self, value1):
self.value1 = value1
if __name__ == '__main__':
foo1 = Foo(5,1)
print foo1.value1, foo1.getId()
foo2 = Foo(15,2)
print foo2.value1, foo2.getId()
Я думаю, что мне действительно нужно, это способ сделать что-то вроде интерфейса С# в Python. Я должен изменить свою парадигму, я полагаю.
Ответы
Ответ 1
Я бы предпочел бы, что вы можете рассмотреть подкласс, а не тот подход, который вы наметили. Однако, не зная вашего конкретного сценария, YMMV: -)
То, о чем вы думаете, это метакласс. Функция __new__
в метаклассе передает полное предложенное определение класса, которое затем может быть переписано до создания класса. Вы можете в это время добавить конструктор для нового.
Пример:
def substitute_init(self, id, *args, **kwargs):
pass
class FooMeta(type):
def __new__(cls, name, bases, attrs):
attrs['__init__'] = substitute_init
return super(FooMeta, cls).__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = FooMeta
def __init__(self, value1):
pass
Замена конструктора, возможно, немного драматична, но язык действительно поддерживает такую глубокую интроспекцию и динамическую модификацию.
Ответ 2
Помимо вопроса, являются ли декораторы классов правильным решением вашей проблемы:
В Python 2.6 и выше существуют декораторы классов с @-syntax, поэтому вы можете написать:
@addID
class Foo:
pass
В старых версиях вы можете сделать это другим способом:
class Foo:
pass
Foo = addID(Foo)
Однако обратите внимание, что это работает так же, как и для декораторов функций, и что декоратор должен возвращать новый (или измененный исходный) класс, что не является тем, что вы делаете в примере. Декоратор addID будет выглядеть так:
def addID(original_class):
orig_init = original_class.__init__
# Make copy of original __init__, so we can call it without recursion
def __init__(self, id, *args, **kws):
self.__id = id
self.getId = getId
orig_init(self, *args, **kws) # Call the original __init__
original_class.__init__ = __init__ # Set the class' __init__ to the new one
return original_class
Затем вы можете использовать соответствующий синтаксис для вашей версии Python, как описано выше.
Но я согласен с другими, что наследование лучше подходит, если вы хотите переопределить __init__
.
Ответ 3
Никто не объяснил, что вы можете динамически определять классы. Таким образом, вы можете иметь декоратор, который определяет (и возвращает) подкласс:
def addId(cls):
class AddId(cls):
def __init__(self, id, *args, **kargs):
super(AddId, self).__init__(*args, **kargs)
self.__id = id
def getId(self):
return self.__id
return AddId
Что можно использовать в Python 2 (комментарий от Blckknght, объясняющий, почему вы должны продолжать делать это в 2. 6+) следующим образом:
class Foo:
pass
FooId = addId(Foo)
И в Python 3 вот так (но будьте осторожны в использовании super()
в ваших классах):
@addId
class Foo:
pass
Так что вы можете съесть свой торт и съесть его - наследство и декораторы!
Ответ 4
Это не хорошая практика, и из-за этого нет механизма для этого. Правильный способ выполнить то, что вы хотите, - наследование.
Загляните в документацию классов .
Маленький пример:
class Employee(object):
def __init__(self, age, sex, siblings=0):
self.age = age
self.sex = sex
self.siblings = siblings
def born_on(self):
today = datetime.date.today()
return today - datetime.timedelta(days=self.age*365)
class Boss(Employee):
def __init__(self, age, sex, siblings=0, bonus=0):
self.bonus = bonus
Employee.__init__(self, age, sex, siblings)
Таким образом, у Boss есть все, что есть Employee
, с его собственным __init__
методом и собственными членами.
Ответ 5
На самом деле здесь довольно хорошая реализация декоратора класса:
https://github.com/agiliq/Django-parsley/blob/master/parsley/decorators.py
Я действительно думаю, что это довольно интересная реализация. Поскольку он подклассифицирует класс, который он украшает, он будет вести себя точно так же, как этот класс, в таких вещах, как isinstance
.
У этого есть дополнительное преимущество: это не редкость для оператора __init__
в пользовательской форме django для внесения изменений или дополнений в self.fields
, поэтому лучше для изменений в self.fields
произойти после того, как все __init__
для класса, о котором идет речь.
Очень умный.
Однако в вашем классе вы действительно хотите, чтобы декорация изменила конструктор, который, как я считаю, не является хорошим вариантом для декоратора класса.
Ответ 6
Я бы согласился, что наследование лучше подходит для поставленной проблемы.
Я нашел этот вопрос действительно удобным, хотя на украшении классов, спасибо всем.
Вот еще пара примеров, основанных на других ответах, включая то, как наследование влияет на вещи в Python 2.7 (и @wraps, который поддерживает исходную строку документации и т.д.):
def dec(klass):
old_foo = klass.foo
@wraps(klass.foo)
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
@dec # No parentheses
class Foo...
Часто вы хотите добавить параметры в ваш декоратор:
from functools import wraps
def dec(msg='default'):
def decorator(klass):
old_foo = klass.foo
@wraps(klass.foo)
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
return decorator
@dec('foo decorator') # You must add parentheses now, even if they're empty
class Foo(object):
def foo(self, *args, **kwargs):
print('foo.foo()')
@dec('subfoo decorator')
class SubFoo(Foo):
def foo(self, *args, **kwargs):
print('subfoo.foo() pre')
super(SubFoo, self).foo(*args, **kwargs)
print('subfoo.foo() post')
@dec('subsubfoo decorator')
class SubSubFoo(SubFoo):
def foo(self, *args, **kwargs):
print('subsubfoo.foo() pre')
super(SubSubFoo, self).foo(*args, **kwargs)
print('subsubfoo.foo() post')
SubSubFoo().foo()
Выходы:
@decorator pre subsubfoo decorator
subsubfoo.foo() pre
@decorator pre subfoo decorator
subfoo.foo() pre
@decorator pre foo decorator
foo.foo()
@decorator post foo decorator
subfoo.foo() post
@decorator post subfoo decorator
subsubfoo.foo() post
@decorator post subsubfoo decorator
Я использовал декоратор функций, так как считаю их более лаконичными. Вот класс, чтобы украсить класс:
class Dec(object):
def __init__(self, msg):
self.msg = msg
def __call__(self, klass):
old_foo = klass.foo
msg = self.msg
def decorated_foo(self, *args, **kwargs):
print('@decorator pre %s' % msg)
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
Более надежная версия, которая проверяет эти скобки и работает, если методы не существуют в украшенном классе:
from inspect import isclass
def decorate_if(condition, decorator):
return decorator if condition else lambda x: x
def dec(msg):
# Only use if your decorator first parameter is never a class
assert not isclass(msg)
def decorator(klass):
old_foo = getattr(klass, 'foo', None)
@decorate_if(old_foo, wraps(klass.foo))
def decorated_foo(self, *args ,**kwargs):
print('@decorator pre %s' % msg)
if callable(old_foo):
old_foo(self, *args, **kwargs)
print('@decorator post %s' % msg)
klass.foo = decorated_foo
return klass
return decorator
assert
проверяет, что декоратор не использовался без скобок. Если это так, то декорируемый класс передается параметру msg
декоратора, который вызывает AssertionError
.
@decorate_if
применяет decorator
если condition
оценивается как True
.
getattr
, callable
тест, и @decorate_if
используются так, что декоратор не сломаться, если foo()
метод не существует на класс украшается.