3

我已经学习了一段时间 Python,并且我开始明白__setattr__正确地覆盖可能很麻烦(至少可以说!)。

有哪些有效的方法可以确保/向自己证明覆盖已正确完成?我特别关心确保覆盖与描述符协议和 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__被正确覆盖时会成功,否则会失败。

4

2 回答 2

3

在这种情况下,有一个简单的解决方案:添加一个带有名称的绑定描述符mydict并测试分配给该名称的名称是否通过描述符(注意: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
于 2014-12-10T13:04:35.767 回答
2

如果您将覆盖__setattr__正确定义为调用__setattr__父类的,那么您可以将您的方法移植到定义其自己的自定义的类层次结构中__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()没有参数的情况下使用。它还假设不涉及元类。

额外__setattr__的以与现有 MRO 一致的方式注入;额外的中间类被注入到原始类和 MRO 的其余部分之间,并继续委托__setattr__调用。

要在测试中使用它,您将使用上述函数生成一个新类,创建一个实例然后在该实例上设置属性:

MyTestClass = inject_tester_class(MyClass)
my_test_instance = MyTestClass()
my_test_instance.foo = 'bar'
my_test_instance.assertSetAttrDelegatedFor('foo', 'bar')

如果foo未委托设置,AssertionError则会引发异常,unittest测试运行程序将其记录为测试失败。

于 2014-12-15T19:47:11.143 回答