在 Python 中模拟成员资格测试:将 __contains__ 正确委派给包含对象

问题描述

我已经习惯了 Python 允许一些巧妙的技巧将功能委托给其他对象.一个例子是委托给包含的对象.

I am used to that Python allows some neat tricks to delegate functionality to other objects. One example is delegation to contained objects.

但是当我想委托 __contains __ 时,我没有运气:

But it seams, that I don't have luck, when I want to delegate __contains __:

class A(object):
    def __init__(self):
       self.mydict = {}
       self.__contains__ = self.mydict.__contains__

a = A()
1 in a

我明白了:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'A' is not iterable

我做错了什么?当我调用 a.__contains __(1) 时,一切都很顺利.我什至尝试在 A 中定义一个 __iter __ 方法,以使 A 看起来更像一个可迭代的,但它没有帮助.我在这里错过了什么?

What I am making wrong? When I call a.__contains __(1), everything goes smooth. I even tried to define an __iter __ method in A to make A more look like an iterable, but it did not help. What I am missing out here?


解决方案

__contains__ 等特殊方法仅在类上定义时才具有特殊性,而不是在实例上(Python 2 中的遗留类除外,你应该不使用它).

Special methods such as __contains__ are only special when defined on the class, not on the instance (except in legacy classes in Python 2, which you should not use anyway).

所以,在课堂上做你的委派:

So, do your delegation at class level:

class A(object):
    def __init__(self):
       self.mydict = {}

    def __contains__(self, other):
       return self.mydict.__contains__(other)

我实际上更喜欢将后者拼写为 return other in self.mydict,但这是一个小风格问题.

I'd actually prefer to spell the latter as return other in self.mydict, but that's a minor style issue.

编辑:如果特殊方法的完全动态的每个实例重定向"(如提供的旧式类)是必不可少的,那么用新式类实现它并不难:你只需要将每个具有这种特殊需求的实例包装在自己的特殊类中即可.例如:

Edit: if and when "totally dynamic per-instance redirecting of special methods" (like old-style classes offered) is indispensable, it's not hard to implement it with new-style classes: you just need each instance that has such peculiar need to be wrapped in its own special class. For example:

class BlackMagic(object):
    def __init__(self):
        self.mydict = {}
        self.__class__ = type(self.__class__.__name__, (self.__class__,), {})
        self.__class__.__contains__ = self.mydict.__contains__

本质上,经过一点点黑魔法将 self.__class__ 重新分配给一个新的类对象(其行为与前一个类似,但有一个空的 dict 并且除了这个之外没有其他实例 self),在你将分配给 self.__magicname__ 的旧式类中的任何位置,改为分配给 self.__class__.__magicname__(并确保它是内置或 staticmethod,不是普通的 Python 函数,当然,除非在某些不同的情况下您确实希望它在实例上调用时接收 self).

Essentially, after the little bit of black magic reassigning self.__class__ to a new class object (which behaves just like the previous one but has an empty dict and no other instances except this one self), anywhere in an old-style class you would assign to self.__magicname__, assign to self.__class__.__magicname__ instead (and make sure it's a built-in or staticmethod, not a normal Python function, unless of course in some different case you do want it to receive the self when called on the instance).

顺便说一下,这个 BlackMagic 类的实例上的 in 操作符比之前提出的任何操作都快解决方案——或者至少我用我通常信任的 -mtimeit 进行测量(直接进入 built-in method,而不是遵循涉及继承和描述符,减少了一些开销).

Incidentally, the in operator on an instance of this BlackMagic class is faster, as it happens, than with any of the previously proposed solutions -- or at least so I'm measuring with my usual trusty -mtimeit (going directly to the built-in method, instead of following normal lookup routes involving inheritance and descriptors, shaves a bit of the overhead).

一个自动实现 self.__class__-per-instance 想法的元类并不难编写(它可以在生成的类的 __new__ 方法中完成繁琐的工作,如果通过 __setattr__ 或许多 many 属性在实例上分配,也可以将所有魔术名称设置为实际分配给类).但这只有在对这个特性的需求真的很普遍的情况下才是合理的(例如,将一个巨大的古老的 Python 1.5.2 项目移植到现代 Python,包括 Python 3).

A metaclass to automate the self.__class__-per-instance idea would not be hard to write (it could do the dirty work in the generated class's __new__ method, and maybe also set all magic names to actually assign on the class if assigned on the instance, either via __setattr__ or many, many properties). But that would be justified only if the need for this feature was really widespread (e.g. porting a huge ancient Python 1.5.2 project that liberally use "per-instance special methods" to modern Python, including Python 3).

我推荐聪明"还是黑魔法"解决方案?不,我不这样做:几乎总是以简单、直接的方式做事更好.但是几乎"在这里是一个重要的词,很高兴手头有这样高级的钩子",用于可能实际需要使用它们的罕见但并非不存在的情况.

Do I recommend "clever" or "black magic" solutions? No, I don't: almost invariably it's better to do things in simple, straightforward ways. But "almost" is an important word here, and it's nice to have at hand such advanced "hooks" for the rare, but not non-existent, situations where their use may actually be warranted.

相关文章