【问题标题】:What tests can I perform to ensure I have overridden __setattr__ correctly?我可以执行哪些测试来确保我已正确覆盖 __setattr__?
【发布时间】:2014-12-10 11:11:59
【问题描述】:

我学习 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__ 被正确覆盖时将成功,否则失败。

【问题讨论】:

  • 你目前在测试什么?
  • 没什么特别的。这是一个普遍的问题。我正在考虑如何做到这一点并且想不出任何方法来做到这一点(例如为属性设置顺序的每个步骤获取日志或打印语句以显示它们完成的顺序 - 不知道如何要做到这一点)。但我也很缺乏经验。
  • @RickTeachey:对不起,我看错了。问题是原始覆盖的__setattr__ 版本没有被调用。由于从未调用过 object.__setattr__,因此也永远不会调用带有 setter 的描述符。
  • 答案都是很好的解决方案,很难在它们之间做出选择。最后我选择了 Bruno's,因为它更具体地解决了这个特殊问题。

标签: python python-3.x unit-testing setattr


【解决方案1】:

在这种情况下,有一个简单的解决方案:添加一个名称在 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

【讨论】:

  • 我已经搜索并找不到太多关于绑定描述符的信息。 “绑定描述符”是否等同于“数据描述符”?如果没有,是否有“非绑定”描述符?另外:我理解上面的测试,这是证明 mro 被违反的一种非常聪明的方法。有没有一种通用的方法可以证明任何给定的实现都没有违反 mro?
  • 是的,“绑定描述符”与“数据描述符”含义相同:实现__set__()的描述符。 wrt/上面的测试,它与mro(“方法解析顺序”,又名查找继承树)几乎没有关系,因为不涉及继承,它是关于尊重绑定描述符。
  • 好的。那我把我的术语弄混了。
【解决方案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 测试运行程序将其记录为测试失败。

【讨论】:

    猜你喜欢
    • 2011-03-12
    • 2023-04-07
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    • 2010-10-29
    • 2017-02-06
    • 1970-01-01
    相关资源
    最近更新 更多