我正在尝试创建一个python装饰器,它将属性添加到类的方法中,以便我可以从方法本身访问和修改这些属性。装饰器代码是
from types import MethodType
class attribute(object):
def __init__(self, **attributes):
self.attributes = attributes
def __call__(self, function):
class override(object):
def __init__(self, function, attributes):
self.__function = function
for att in attributes:
setattr(self, att, attributes[att])
def __call__(self, *args, **kwargs):
return self.__function(*args, **kwargs)
def __get__(self, instance, owner):
return MethodType(self, instance, owner)
retval = override(function, self.attributes)
return retval
我在随后的玩具示例中尝试了这个装饰器。
class bar(object):
@attribute(a=2)
def foo(self):
print self.foo.a
self.foo.a = 1
虽然我能够从foo()中访问属性'a'的值,但我无法将其设置为其他值。 的确,当我致电bar().foo()
时,我得到以下的AttributeError。
AttributeError: 'instancemethod' object has no attribute 'a'
这是为什么?更重要的是,我如何实现目标?
修改
更具体地说,我试图找到一种简单的方法来实现位于类方法中的静态变量。继续上面的示例,我希望实例化b = bar()
,同时调用foo()
和doo()
方法,然后再访问b.foo.a
和b.doo.a
。
class bar(object):
@attribute(a=2)
def foo(self):
self.foo.a = 1
@attribute(a=4)
def doo(self):
self.foo.a = 3
答案 0 :(得分:4)
执行此操作的最佳方法是根本不执行此操作。
首先,不需要属性装饰器;你可以自己分配:
class bar(object):
def foo(self):
print self.foo.a
self.foo.a = 1
foo.a = 2
然而,这仍然遇到相同的错误。你需要这样做:
self.foo.__dict__['a'] = 1
你可以改为使用元类......但这很快就会变得混乱。
另一方面,有更清洁的替代品。
您可以使用默认值:
def foo(self, a):
print a[0]
a[0] = 2
foo.func_defaults = foo.func_defaults[:-1] + ([2],)
当然,我首选的方法是完全避免这种情况,并使用一个可调用的类("仿函数"用C ++语言):
class bar(object):
def __init__(self):
self.foo = self.foo_method(self)
class foo_method(object):
def __init__(self, bar):
self.bar = bar
self.a = 2
def __call__(self):
print self.a
self.a = 1
或者只使用经典类属性:
class bar(object):
def __init__(self):
self.a = 1
def foo(self):
print self.a
self.a = 2
如果要隐藏a
派生类,请使用Python术语中调用的任何私有属性:
class bar(object):
def __init__(self):
self.__a = 1 # this will be implicitly mangled as __bar__a or similar
def foo(self):
print self.__a
self.__a = 2
编辑:您想要静态属性吗?
class bar(object):
a = 1
def foo(self):
print self.a
self.a = 2
编辑2 :如果您希望静态属性仅对当前函数可见,则可以使用PyExt modify_function :
import pyext
def wrap_mod(*args, **kw):
def inner(f):
return pyext.modify_function(f, *args, **kw)
return inner
class bar(object):
@wrap_mod(globals={'a': [1]})
def foo(self):
print a[0]
a[0] = 2
它有点丑陋和hackish。但它确实有效。
我的建议只是使用双下划线:
class bar(object):
__a = 1
def foo(self):
print self.__a
self.__a = 2
虽然这个 对其他功能可见,但它对其他任何东西都是不可见的(实际上,它在那里,但是它被破坏了)。
最终编辑:使用此:
import pyext
def wrap_mod(*args, **kw):
def inner(f):
return pyext.modify_function(f, *args, **kw)
return inner
class bar(object):
@wrap_mod(globals={'a': [1]})
def foo(self):
print a[0]
a[0] = 2
foo.a = foo.func_globals['a']
b = bar()
b.foo() # prints 1
b.foo() # prints 2
# external access
b.foo.a[0] = 77
b.foo() # prints 77
答案 1 :(得分:2)
虽然您可以将self.foo.a = 1
替换为self.foo.__dict__['a'] = 1
来实现目标,但通常不建议这样做。
答案 2 :(得分:1)
如果您使用的是Python2(而不是Python3) - 每当从实例中检索方法时,都会创建一个新的instance method
对象,它是类体中定义的原始函数的包装。
实例方法是函数的一个相当透明的代理 - 您可以通过它检索函数的属性,但不能设置它们 - 这就是为什么在self.foo.__dict__
中设置项目的原因。
或者,您可以使用以下方法访问函数对象:self.foo.im_func
- 实例方法的im_func
属性指向基础函数。
答案 3 :(得分:0)
根据其他贡献者的答案,我提出了以下解决方法。首先,将一个字典中的字典包含在解析包含的字典中的非存在属性中,例如以下代码。
class DictWrapper(object):
def __init__(self, d):
self.d = d
def __getattr__(self, key):
return self.d[key]
此代码对Lucas Jones的信用。
然后使用addstatic
属性实现statics
装饰器,该属性将存储静态属性。
class addstatic(object):
def __init__(self, **statics):
self.statics = statics
def __call__(self, function):
class override(object):
def __init__(self, function, statics):
self.__function = function
self.statics = DictWrapper(statics)
def __call__(self, *args, **kwargs):
return self.__function(*args, **kwargs)
def __get__(self, instance, objtype):
from types import MethodType
return MethodType(self, instance)
retval = override(function, self.statics)
return retval
以下代码是如何在方法上使用addstatic
装饰器的示例。
class bar(object):
@attribute(a=2, b=3)
def foo(self):
self.foo.statics.a = 1
self.foo.statics.b = 2
然后,使用bar
类的实例产生:
>>> b = bar()
>>> b.foo.statics.a
2
>>> b.foo.statics.b
3
>>> b.foo()
>>> b.foo.statics.a
3
>>> b.foo.statics.b
5
使用这个静态字典的原因遵循jsbueno的答案,这表明我想要的是需要重载包含foo函数的点运算符和实例方法,我不确定是否可能。当然,方法的属性可以在self.foo.__dict__
中设置,但由于不推荐(正如brainovergrow所建议的),我想出了这个解决方法。我不确定这是否会被推荐,我想这是为了评论。