我的课程有很多非常相似的属性:
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'
答案 0 :(得分:2)
你总是可以使用wraps技巧将参数传递给装饰器,如下所示:
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
答案 1 :(得分:1)
如果您想使用@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
装饰者。
答案 2 :(得分:0)
我最终得到了一个元类,使子类更容易。感谢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)