Базовый дескриптор/декоратор
Вам просто нужно помнить, какую функцию вы должны декорировать. Ваша функция создается в __get__
, поэтому использование обертки в качестве декоратора не поможет, вместо этого вам нужно применить ее в методе __get__
. Кроме того, для этого вы можете использовать либо functools.update_wrapper
, либо decorators.decorator
. Они работают очень похоже, за исключением того, что вы должны сохранить результат decorators.decorator
, тогда как functools.update_wrapper
возвращает None
. Оба имеют подпись f(wrapper, wrapped)
.
from functools import update_wrapper
class class_or_instance(object):
def __init__(self, fn):
self.fn = fn
def __get__(self, obj, cls):
if obj is not None:
f = lambda *args, **kwds: self.fn(obj, *args, **kwds)
else:
f = lambda *args, **kwds: self.fn(cls, *args, **kwds)
# update the function to have the correct metadata
update_wrapper(f, self.fn)
return f
class A(object):
@class_or_instance
def func1(self,*args):
"""some docstring"""
pass
Теперь, если вы сделаете:
print A.func1.__doc__
Вы увидите «какую-то строку документации». Ура!
Кэшированный декоратор свойств
Ключевым моментом здесь является то, что вы можете влиять только на то, что возвращается. Поскольку class_or_instance
на самом деле не служит функцией, на самом деле не имеет значения, что вы с ним делаете. Имейте в виду, что этот метод приводит к тому, что функция каждый раз перестраивается. Вместо этого я предлагаю вам добавить немного магии и связать/кэшировать функцию после первого вызова, который на самом деле просто включает добавление вызова setattr
.
from functools import update_wrapper
import types
class class_or_instance(object):
# having optional func in case is passed something that doesn't have a correct __name__
# (like a lambda function)
def __init__(self, name_or_func):
self.fn = fn
self.name = fn.__name__
def __get__(self, obj, cls):
print "GET!!!"
if obj is not None:
f = lambda *args, **kwds: self.fn(obj, *args, **kwds)
update_wrapper(f, self.fn)
setattr(obj, self.name, types.MethodType(f, obj, obj.__class__))
else:
f = lambda *args, **kwds: self.fn(cls, *args, **kwds)
update_wrapper(f, self.fn)
return f
И тогда мы можем проверить это... neato:
A.func1 #GET!!!
obj = A()
obj.func1 #GET!!!
obj.func1 is obj.func1 # True
A.func1 # GET!!!
obj2 = A()
obj2.func1 is not obj.fun1 # True + GET!!!
person
Jeff Tratner
schedule
18.06.2013