我想使用装饰器包装除__init__
之外的各种对象的每个方法。
class MyObject(object):
def method(self):
print "method called on %s" % str(self)
@property
def result(self):
return "Some derived property"
def my_decorator(func):
def _wrapped(*args, **kwargs):
print "Calling decorated function %s" % func
return func(*args, **kwargs)
return _wrapped
class WrappedObject(object):
def __init__(self, cls):
for attr, item in cls.__dict__.items():
if attr != '__init__' and (callable(item) or isinstance(item, property)):
setattr(cls, attr, my_decorator(item))
self._cls = cls
def __call__(self, *args, **kwargs):
return self._cls(*args, **kwargs)
inst = WrappedObject(MyObject)()
但是,包装属性实例结果等同于:
@my_decorator
@property
def result(self):
return "Some derived property"
当期望的结果与此相当时:
@property
@my_decorator
def result(self):
return "Some derived property"
似乎属性对象的属性是只读的,因为在属性包装后它会阻止修改函数。我对已经要求的hackery水平感到不太满意,而且我还是不想深入研究属性对象。
我能看到的唯一其他解决方案是动态生成一个我希望避免的元类。我错过了一些明显的东西吗?
答案 0 :(得分:4)
此示例中还有一些其他问题,但要提出疑问,您只需要做 是,当你包裹一个属性
当您包装属性时,请改为包装其__get__方法:
class MyObject(object):
def method(self):
print "method called on %s" % str(self)
@property
def result(self):
return "Some derived property"
def common(self, a=None):
print self
def my_decorator(func):
def _wrapped(*args, **kwargs):
print "Calling decorated function %s" % func
return func(*args, **kwargs)
return _wrapped
class WrappedObject(object):
def __init__(self, cls):
for attr, item in cls.__dict__.items():
if attr != '__init__' and callable(item):
setattr(cls, attr, my_decorator(item))
elif isinstance(item, property):
new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__)
setattr(cls, attr, new_property)
self._cls = cls
def __call__(self, *args, **kwargs):
return self._cls(*args, **kwargs)
inst = WrappedObject(MyObject)()
这是对您的代码进行最简单的修改。 然而,我会将它更改为它正在包装的类的子类,以避免重写其属性。您可以通过简单地使用名称来调用类型,使用基数的元组和作为参数的字典来编程创建子类。
实际上,子类化给定的类几乎不需要修改给定的代码,
但是对于我指出的type
电话。我刚刚在这里测试了它 - 将你的WrappedObject类更改为:
class WrappedObject(object):
def __init__(self, cls):
dct = cls.__dict__.copy()
for attr, item in dct.items():
if attr != '__init__' and callable(item):
dct[attr] = my_decorator(item)
elif isinstance(item, property):
new_property = property(my_decorator(item.__get__), item.__set__, item.__delattr__)
dct[attr] = new_property
self._cls = type("wrapped_" + cls.__name__, (cls,), dct)
def __call__(self, *args, **kwargs):
return self._cls(*args, **kwargs)
答案 1 :(得分:1)
经过一些尝试和错误后,我提出了以下解决方案。首先,创建一个将模拟装饰描述符的辅助类:
class DecoratedDescriptor(object):
def __init__(self, descriptor, decorator):
self.funcs = {}
for attrname in '__get__', '__set__', '__delete__':
self.funcs[attrname] = decorator(getattr(descriptor, attrname))
def __get__(self, *args, **kwargs):
return self.funcs['__get__'](*args, **kwargs)
def __set__(self, *args, **kwargs):
return self.funcs['__set__'](*args, **kwargs)
def __delete__(self, *args, **kwargs):
return self.funcs['__delete__'](*args, **kwargs)
然后,如果你看到一个属性,请将其包装在其中:
# Fragment of your WrappedObject.__init__ method:
if attr != '__init__' and callable(item):
setattr(cls, attr, my_decorator(item))
elif isinstance(item, property):
setattr(cls, attr, DecoratedDescriptor(item, my_decorator))
这样的工作原理如下:
>>> inst = WrappedObject(MyObject)()
>>> print inst.result
Calling decorated function <method-wrapper '__get__' of property object at 0x00BB6930>
Some derived property
有!没有元类:)
答案 2 :(得分:0)
你可以引入“懒惰”装饰器,它们在你自己的装饰器之后应用,例如:
class Lazy(object):
def __init__(self, decorator):
self.decorator = decorator
def __call__(self, method):
self.method = method
return self
def my_decorator(func):
def _wrapped(*args, **kwargs):
print "Calling decorated function %s" % func
return func(*args, **kwargs)
if isinstance(func, Lazy):
lazy = func
func = lazy.method
return lazy.decorator(_wrapped)
return _wrapped
lazy_property = Lazy(property)
..然后使用@lazy_property
代替@property
。 (警告:未经测试的代码,但我希望你能得到这个想法......)