[英]Python decorator with arguments
我有一个类具有很多非常相似的属性:
class myClass(object):
def compute_foo(self):
return 3
def compute_bar(self):
return 4
@property
def foo(self):
try:
return self._foo
except AttributeError:
self._foo = self.compute_foo()
return self._foo
@property
def bar(self):
try:
return self._bar
except AttributeError:
self._bar = self.compute_bar()
return self._bar
...
所以我想我会写一个装饰器来做属性定义工作。
class myDecorator(property):
def __init__(self, func, prop_name):
self.func = func
self.prop_name = prop_name
self.internal_prop_name = '_' + prop_name
def fget(self, obj):
try:
return obj.__getattribute__(self.internal_prop_name)
except AttributeError:
obj.__setattr__(self.internal_prop_name, self.func(obj))
return obj.__getattribute__(self.internal_prop_name)
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.func is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
class myClass(object):
def compute_foo(self):
return 3
foo = myDecorator(compute_foo, 'foo')
def compute_bar(self):
return 4
bar = myDecorator(compute_bar, 'bar')
这很好用,但是当我想使用@myDecorator('foo')
语法时,它变得更复杂,无法确定__call__
方法应该返回什么以及如何将属性附加到它的类。
目前我有:
class myDecorator(object):
def __init__(self, prop_name):
self.prop_name = prop_name
self.internal_prop_name = '_' + prop_name
def __call__(self, func):
self.func = func
return #???
def fget(self, obj):
try:
return obj.__getattribute__(self.internal_prop_name)
except AttributeError:
obj.__setattr__(self.internal_prop_name, self.func(obj))
return obj.__getattribute__(self.internal_prop_name)
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.func is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
class myClass(object):
@myDecorator('foo')
def compute_foo(self):
return 3
c = myClass()
print(c.foo)
它返回: AttributeError: 'myClass' object has no attribute 'foo'
您总是可以使用包装技巧将参数传递给装饰器,如下所示:
from functools import wraps
class myDecorator(property):
def __init__(self, prop_name):
self.prop_name = prop_name
def __call__(self, wrappedCall):
@wraps(wrappedCall)
def wrapCall(*args, **kwargs):
klass = args[0]
result = wrappedCall(*args, **kwargs)
setattr(klass, self.prop_name, result)
return wrapCall
class myClass(object):
@myDecorator('foo')
def compute_foo(self):
return 3
c = myClass()
c.compute_foo()
print c.foo
如果要使用@decorator
语法,则无法将该属性重新映射到该类的其他名称。 这意味着您的compute_x
方法必须重命名为与属性相同。
编辑:可以重新映射名称,但您也需要使用类装饰器。
class MyProperty(property):
def __init__(self, name, func):
super(MyProperty, self).__init__(func)
self.name = name
self.internal_prop_name = '_' + name
self.func = func
def fget(self, obj):
try:
return obj.__getattribute__(self.internal_prop_name)
except AttributeError:
obj.__setattr__(self.internal_prop_name, self.func(obj))
return obj.__getattribute__(self.internal_prop_name)
def __get__(self, obj, objtype=None)
if obj is None:
return self
if self.func is None:
raise AttributeError('unreadable')
return self.fget(obj)
def myproperty(*args)
name = None
def deco(func):
return MyProperty(name, func)
if len(args) == 1 and callable(args[0]):
name = args[0].__name__
return deco(args[0])
else:
name = args[0]
return deco
class Test(object):
@myproperty
def foo(self):
return 5
没有类装饰器,name参数唯一相关的时候就是你的内部变量名与函数名不同,所以你可以这样
@myproperty('foobar')
def foo(self):
return 5
它会寻找_foobar
而不是_foo
,但属性名称仍然是foo
。
但是,你可以重新映射属性名称的方式,但你必须使用一个类装饰为好。
def clsdeco(cls):
for k, v in cls.__dict__.items():
if isinstance(v, MyProperty) and v.name != k:
delattr(cls, k)
setattr(cls, v.name, v)
return cls
@clsdeco
class Test(...)
@myproperty('foo')
def compute_foo(self):
pass
这将遍历类的所有属性并找到任何MyProperty
实例,并检查集合名称是否与映射名称相同,否则,它会将属性重新绑定到传递给myproperty
装饰器的名称。
我最终得到了一个元类,使子类更容易。 感谢Brendan Abel暗示这个方向。
import types
class PropertyFromCompute(property):
def __init__(self, func):
self.func = None
self.func_name = func.__name__
self.internal_prop_name = self.func_name.replace('compute', '')
def fget(self, obj):
try:
return obj.__getattribute__(self.internal_prop_name)
except AttributeError:
obj.__setattr__(self.internal_prop_name, self.func())
return obj.__getattribute__(self.internal_prop_name)
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.func is None:
try:
self.func = obj.__getattribute__(self.func_name)
except AttributeError:
raise AttributeError("unreadable attribute")
return self.fget(obj)
class WithPropertyfromCompute(type):
def __new__(cls, clsname, bases, dct):
add_prop = {}
for name, obj in dct.items():
if isinstance(obj, types.FunctionType) and name.startswith('compute_'):
add_prop.update({name.replace('compute_',''): PropertyFromCompute(obj)})
dct.update(add_prop)
return super().__new__(cls, clsname, bases, dct)
class myClass(object, metaclass=WithPropertyfromCompute):
def compute_foo(self):
raise NotImplementedError('Do not instantiate the base class, ever !')
class myChildClass(myClass):
def compute_foo(self):
return 4
base = myClass()
try:
print(base.foo)
except NotImplementedError as e:
print(e)
print(myClass.foo)
child = myChildClass()
print(child.foo)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.