我正在编写一个 python 类,它使用
__setattr__
和 __getattr__
来提供自定义属性访问。
但是,某些属性无法以通用方式处理,因此我希望对这些属性使用描述符。
出现的问题是,对于描述符,描述符的
__get__
将被调用以支持实例 __getattr__
,但是当分配给属性时,将调用 __setattr__
以支持描述符 __set__
。
一个例子:
class MyDesc(object):
def __init__(self):
self.val = None
def __get__(self, instance, owner):
print "MyDesc.__get__"
return self.val
def __set__(self, instance, value):
print "MyDesc.__set__"
self.val = value
class MyObj(object):
foo = MyDesc()
def __init__(self, bar):
object.__setattr__(self, 'names', dict(
bar=bar,
))
object.__setattr__(self, 'new_names', dict())
def __setattr__(self, name, value):
print "MyObj.__setattr__ for %s" % name
self.new_names[name] = value
def __getattr__(self, name):
print "MyObj.__getattr__ for %s" % name
if name in self.new_names:
return self.new_names[name]
if name in self.names:
return self.names[name]
raise AttributeError(name)
if __name__ == "__main__":
o = MyObj('bar-init')
o.bar = 'baz'
print o.bar
o.foo = 'quux'
print o.foo
打印:
MyObj.__setattr__ for bar
MyObj.__getattr__ for bar
baz
MyObj.__setattr__ for foo
MyDesc.__get__
None
描述符的
__set__
永远不会被调用。
由于
__setattr__
定义不仅仅是一组有限名称的重写行为,因此没有明确的地方可以遵循 object.__setattr__
是否有推荐的方法来使用描述符分配属性(如果可用),否则
__setattr__
?
更新
十多年后重新审视这个答案,我意识到我包含了一个食谱,但没有解释OP遇到的突出行为:在实例的
__get__
之前调用描述符的__getattr__
,是的。但与 __setattr__
实际相反的是 __getattribute__
,而不是 __getattr__
。在 object
的 __getattribute__
内部,首先是属性搜索顺序的代码,以及负责调用描述符的 __get__
本身的代码。在检查任何可能的描述符 __getattr__
和实例的 __getattribute__
后,__slots__
由 __dict__
本身中的代码调用,作为最后的手段。
原始答案:只做OP想要实现的目标
我想我会通过一种机制来自动标记哪些是 每个类中的描述符,并以调用的方式包装
__setattr__
这些名称的对象的正常行为。
这可以通过元类(以及
__setattr__
的装饰器)轻松实现
def setattr_deco(setattr_func):
def setattr_wrapper(self, attr, value):
if attr in self._descriptors:
return object.__setattr__(self, attr, value)
return setattr_func(self, attr, value)
return setattr_wrapper
class MiscSetattr(type):
def __new__(metacls, name, bases, dct):
descriptors = set()
for key, obj in dct.items():
if key == "__setattr__":
dct[key] = setattr_deco(obj)
elif hasattr(obj, "__get__"):
descriptors.add(key)
dct["_descriptors"] = descriptors
return type.__new__(metacls, name, bases, dct)
# and use MiscSetattr as metaclass for your classes
可能的方法之一:
def __setattr__(self, name, value):
print "MyObj.__setattr__ for %s" % name
for cls in self.__class__.__mro__ + (self, ):
if name in cls.__dict__:
return object.__setattr__(self, name, value)
print 'New name', name, value
self.new_names[name] = value
它检查名称是否已在类、基类或实例中定义,然后调用
object.__setattr__
,它将执行描述符 __set__
。
另一种方式:
def __setattr__(self, name, value):
print "MyObj.__setattr__ for %s" % name
try:
object.__getattribute__(self, name)
except AttributeError:
print 'New name', name, value
self.new_names[name] = value
else:
object.__setattr__(self, name, value)
但是它会调用描述符的
__get__
。
附注
我不确定是否需要检查所有
__mro__
成员,因为 MyObj
将包含 __dict__
中继承的类成员。
也许
for cls in (self.__class__, self):...
就足够了。