Python 带参数的装饰器
Python decorator with arguments
我有一个 class 有很多非常相似的属性:
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__
方法应该 return 以及如何附加 属性 到它的 class.
目前我有:
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)
它 returns: AttributeError: 'myClass' object has no attribute 'foo'
如果您想使用 @decorator
语法,您将无法将 属性 重新映射到 class 上的不同名称。这意味着您的 compute_x
方法必须重命名为与属性相同的名称。
编辑:可以重新映射名称,但您还需要使用 class 装饰器。
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
没有 class 装饰器,只有当你的内部变量名与函数名不同时,name 参数才有意义,所以你可以像
@myproperty('foobar')
def foo(self):
return 5
并且它会寻找 _foobar
而不是 _foo
,但属性名称仍然是 foo
.
但是, 有一种方法可以重新映射属性名称,但您还必须使用 class 装饰器。
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
这将遍历 class 上的所有属性并找到任何 MyProperty
实例并检查集合名称是否与映射名称相同,如果不相同,它将重新绑定将 属性 传递给 myproperty
装饰器的名称。
您始终可以使用包装技巧将参数传递给装饰器,如下所示:
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
我最终得到了一个元类,使子类化更容易。感谢 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)
我有一个 class 有很多非常相似的属性:
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__
方法应该 return 以及如何附加 属性 到它的 class.
目前我有:
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)
它 returns: AttributeError: 'myClass' object has no attribute 'foo'
如果您想使用 @decorator
语法,您将无法将 属性 重新映射到 class 上的不同名称。这意味着您的 compute_x
方法必须重命名为与属性相同的名称。
编辑:可以重新映射名称,但您还需要使用 class 装饰器。
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
没有 class 装饰器,只有当你的内部变量名与函数名不同时,name 参数才有意义,所以你可以像
@myproperty('foobar')
def foo(self):
return 5
并且它会寻找 _foobar
而不是 _foo
,但属性名称仍然是 foo
.
但是, 有一种方法可以重新映射属性名称,但您还必须使用 class 装饰器。
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
这将遍历 class 上的所有属性并找到任何 MyProperty
实例并检查集合名称是否与映射名称相同,如果不相同,它将重新绑定将 属性 传递给 myproperty
装饰器的名称。
您始终可以使用包装技巧将参数传递给装饰器,如下所示:
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
我最终得到了一个元类,使子类化更容易。感谢 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)