我一直在研究Python一段时间,我已经明白正确覆盖__setattr__
可能很麻烦(to say the least!)。
有哪些有效方法可以确保/证明覆盖已正确完成?我特别关注确保覆盖与描述符协议和MRO保持一致。
(标记为Python 3.x因为我正在使用它,但问题当然也适用于其他版本。)
“覆盖”表现出默认行为的示例代码(但我该如何证明?):
class MyClass():
def __setattr__(self,att,val):
print("I am exhibiting default behavior!")
super().__setattr__(att,val)
覆盖违反描述符协议的实例(实例存储查找在描述符查找之前发生 - 但我该如何测试呢?):
class MyClass():
def __init__(self,mydict):
self.__dict__['mydict'] = mydict
@property
def mydict(self):
return self._mydict
def __setattr__(self,att,val):
if att in self.mydict:
self.mydict[att] = val
else:
super().__setattr__(att, val)
理想的答案将提供一个通用测试,当__setattr__
被正确覆盖时,该测试将成功,否则将失败。
答案 0 :(得分:3)
在这种情况下,有一个简单的解决方案:添加一个名称在mydict
的绑定描述符,并测试分配给该名称的是通过描述符(NB:Python 2.x代码,我没有这里安装了Python 3):
class MyBindingDescriptor(object):
def __init__(self, key):
self.key = key
def __get__(self, obj, cls=None):
if not obj:
return self
return obj.__dict__[self.key]
def __set__(self, obj, value):
obj.__dict__[self.key] = value
sentinel = object()
class MyClass(object):
test = MyBindingDescriptor("test")
def __init__(self, mydict):
self.__dict__['mydict'] = mydict
self.__dict__["test"] = sentinel
def __setattr__(self, att, val):
if att in self.mydict:
self.mydict[att] = val
else:
super(MyClass, self).__setattr__(att, val)
# first test our binding descriptor
instance1 = MyClass({})
# sanity check
assert instance1.test is sentinel, "instance1.test should be sentinel, got '%s' instead" % instance1.test
# this one should pass ok
instance1.test = NotImplemented
assert instance1.test is NotImplemented, "instance1.test should be NotImplemented, got '%s' instead" % instance1.test
# now demonstrate that the current implementation is broken:
instance2 = MyClass({"test":42})
instance2.test = NotImplemented
assert instance2.test is NotImplemented, "instance2.test should be NotImplemented, got '%s' instead" % instance2.test
答案 1 :(得分:2)
如果您正确定义覆盖__setattr__
调用父类的__setattr__
,那么您可以将方法移植到定义自己的自定义{{1 }}:
__setattr__
此函数跳过一些箍以插入一个中间类,该中级类将拦截任何正确委派的def inject_tester_class(cls):
def __setattr__(self, name, value):
self._TesterClass__setattr_args.append((name, value))
super(intermediate, self).__setattr__(name, value)
def assertSetAttrDelegatedFor(self, name, value):
assert \
[args for args in self._TesterClass__setattr_args if args == (name, value)], \
'__setattr__(name, value) was never delegated'
body = {
'__setattr__': __setattr__,
'assertSetAttrDelegatedFor': assertSetAttrDelegatedFor,
'_TesterClass__setattr_args': []
}
intermediate = type('TesterClass', cls.__bases__, body)
testclass = type(cls.__name__, (intermediate,), vars(cls).copy())
# rebind the __class__ closure
def closure():
testclass
osa = testclass.__setattr__
new_closure = tuple(closure.__closure__[0] if n == '__class__' else c
for n, c in zip(osa.__code__.co_freevars, osa.__closure__))
testclass.__setattr__ = type(osa)(
osa.__code__, osa.__globals__, osa.__name__,
osa.__defaults__, new_closure)
return testclass
调用。即使您没有默认__setattr__
以外的任何基类,它也会起作用(我们不会让我们替换object
以获得更简单的测试方法)
它确实假设您使用__setattr__
进行委派,而您使用super().__setattr__()
而不使用参数。它还假设没有涉及元类。
额外super()
以与现有MRO一致的方式注入;额外的中间类在原始类和MRO的其余部分之间注入,并从此委托__setattr__
调用。
要在测试中使用它,您需要使用上述函数生成一个新类,创建一个实例,然后在该实例上设置属性:
__setattr__
如果未设置MyTestClass = inject_tester_class(MyClass)
my_test_instance = MyTestClass()
my_test_instance.foo = 'bar'
my_test_instance.assertSetAttrDelegatedFor('foo', 'bar')
设置,则会引发foo
异常,AssertionError
测试运行器会将其记录为测试失败。