在Python中猴子补丁__eq__

3

我有些困惑,为什么我可以在类外重新定义(猴子补丁)__eq__,但无法通过__init__或方法更改其定义:

class SpecialInteger:
    def __init__(self,x):
        self.x = x
        self.__eq__ = self.equals_normal
    def equals_normal(self,other):
        return self.x == other.x
    def equals_special(self,other):
        return self.x != other.x
    def switch_to_normal(self):
        self.__eq__ = self.equals_normal
    def switch_to_special(self):
        self.__eq__ = self.equals_special

a = SpecialInteger(3)
b = SpecialInteger(3)

print(a == b)  # false

a.switch_to_normal()
print(a == b)  # false

SpecialInteger.__eq__ = SpecialInteger.equals_normal
print(a == b)  # true

SpecialInteger.__eq__ = SpecialInteger.equals_special
print(a == b)  # false

我是否只是错误地使用了self,还是有其他原因导致它像这样工作?

4个回答

3

要在类内部完成此操作,您只需在类内定义__eq__方法即可。

class SpecialInteger:
    def __init__(self,x):
        self.x = x

    def __eq__(self, other):
        # do stuff, call whatever other methods you want

编辑: 我明白你的意思,你希望在实例级别上覆盖方法(这是一种“魔法”方法)。我不认为在语言的基本结构中这是可能的,根据这个讨论

你的猴子补丁之所以在那个示例中有效,是因为它被传递到类级别,而不是实例级别,而 self 是指实例。


1
所有的方法定义都在类级别上进行(字面上,名称是属于类的一个字典中的一个键)。这也适用于在类级别上放置的任何其他内容。这就是为什么例如在类的方法之外进行变量赋值会产生一个类变量的原因。

1

补充一下一个很好的现有答案,但这不起作用,因为您正在修改类实例,而不是类。

为了获得所需的行为,您可以在__init__期间修改类,但这是非常不足的(因为它修改了类,因此所有类的实例都会受到影响),最好使这些更改在类范围内可见。

例如,以下内容是等效的:

class SpecialInteger1:
    def __init__(self,x):
        self.x = x
        self.__class__.__eq__ = self.equals_normal
    ...

class SpecialInteger2:
    def __init__(self,x):
        self.x = x
    def equals_normal(self,other):
        return self.x == other.x
    def __eq__(self, other):
        return self.equals_normal(other)

在所有示例中,您应该更喜欢使用SpecialInteger2,因为它更明确地说明了其功能。

然而,这些都没有真正解决您要解决的问题:如何在实例级别上创建一个可以切换的特定相等比较。答案是通过使用枚举(在Python 3中):

from enum import Enum

class Equality(Enum):
    NORMAL = 1
    SPECIAL = 2

class SpecialInteger:
    def __init__(self, x, eq = Equality.NORMAL):
        self.x = x
        self.eq = eq
    def equals_normal(self, other):
        return self.x == other.x
    def equals_special(self, other):
        return self.x != other.x
    def __eq__(self, other):
        return self.__comp[self.eq](self, other)
    # Define a dictionary for O(1) access 
    # to call the right method.
    __comp = {
        Equality.NORMAL: equals_normal,
        Equality.SPECIAL: equals_special
    }

让我们快速地浏览一下,因为这里有三个部分:
  1. 一个实例成员变量 eq,可以动态修改。
  2. 一个实现了 __eq__ 的函数,根据 self.eq 的值选择正确的相等函数。
  3. 一个命名空间混淆的字典(以 __ 开头的类/成员变量,在本例中是 self.__comp),允许高效地查找所需的相等方法。
这个字典可以很容易地被替换掉,特别是在你只想支持 1-5 种不同可能比较的情况下,并用惯用的 if/then 语句代替,但是,如果你想支持更多的比较选项(比如说,300),那么字典将比 if/then 比较(线性搜索,O(n))更有效率 O(1)
如果你想使用设置器(就像原始示例中一样),并且实际上将成员函数隐藏在用户之外,你还可以通过直接将函数存储为变量来实现这一点。

0

保持相同功能最简单的方法是从__eq__中引用其他变量。它可以是一些引用变量,也可以是一个保存的方法。

class SpecialInteger:
    def __init__(self,x):
        self.x = x
        self._equal_method = self.equals_normal

    # ...

    def switch_to_normal(self):
        self._equal_method = self.equals_normal

    def switch_to_special(self):
        self._equal_method = self.equals_special

    def __eq__(self, other):
        return self._equal_method(other)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接