Python:__eq__ 和 __contains__ 在自定义 class 上的奇怪行为
Python: Strange behaviour of __eq__ and __contains__ on a custom class
我有以下自定义 类(精简了一些),实现了一个表达式树:
from abc import ABC, abstractmethod
class Operator:
def __init__(self, str_reps):
self.str_reps = str_reps
def __str__(self):
return self.str_reps[0]
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __hash__(self):
return hash(str(self))
NOT = Operator(["¬", "~", "not", "!"])
AND = Operator(["∧", "&", "and"])
OR = Operator(["∨", "|", "or"])
class Node(ABC):
@abstractmethod
def __eq__(self, other):
pass
@abstractmethod
def __hash__(self):
pass
def __ne__(self, other):
return not self == other
@abstractmethod
def __str__(self):
pass
@abstractmethod
def __invert__(self):
pass
def bracket_if_necessary(self):
return str(self)
class Leaf(Node):
def __init__(self, v):
self.val = v
def __eq__(self, other):
if not isinstance(other, Leaf):
return False
return self.val == other.val
def __hash__(self):
return hash(self.val)
def __str__(self):
return str(self.val)
def __invert__(self):
return UnaryNode(self)
class UnaryNode(Node):
def __init__(self, child):
self.child = child
self.hash = hash(NOT) + hash(self.child)
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
def __hash__(self):
return self.hash
def __str__(self):
return str(NOT) + self.child.bracket_if_necessary()
def __invert__(self):
return self.child
class VariadicNode(Node):
def __init__(self, op, children):
self.op = op
self.children = children
self.hash = hash(self.op) + sum(hash(child) for child in self.children)
def __eq__(self, other):
if not isinstance(other, VariadicNode):
return False
return self.op is other.op and set(self.children) == set(other.children)
def __hash__(self):
return self.hash
def __str__(self):
return (" " + str(self.op) + " ").join(child.bracket_if_necessary() for child in self)
def __invert__(self):
return VariadicNode(AND if self.op is OR else OR, tuple(~c for c in self))
def bracket_if_necessary(self):
return "(" + str(self) + ")"
def __iter__(self):
return iter(self.children)
def __contains__(self, item):
return item in self.children
如果我 运行 这样做并尝试
Leaf("36") == Leaf("36)
~Leaf("36") == ~Leaf("36")
~~Leaf("36") == Leaf("36")
他们都return如预期的那样。
但是,我运行正在研究利用这些节点的代码中的错误:
# Simplify procedure in DPLL Algorithm
def _simplify(cnf, l):
# print for debugging
for c in cnf:
print("b", l, ~l, type(l), "B", c, type(c), l in c)
return VariadicNode(AND, tuple(_filter(c, ~l) for c in cnf if l not in c))
# Removes the chosen unit literal (negated above) from clause c
def _filter(c, l):
# print for debugging
for x in c:
print("a", l, type(l), "A", x, type(x), x==l)
return VariadicNode(c.op, tuple(x for x in c if x != l))
此处 cnf
作为 VariadicNode(AND)
给出,所有 children 为 VariadicNode(OR)
。 VariadicNode
的 Children 总是作为元组给出。
这两个打印结果如下:
a ¬25 <class 'operators.UnaryNode'> A ¬25 <class 'operators.UnaryNode'> False
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 ∨ ¬36 <class 'operators.VariadicNode'> False
这不应该发生(第一行的 ¬25 == ¬25
和第二行的 ¬25 in (¬25 ∨ ¬36)
都应该 return True)。但是输出中还有一行:
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 <class 'operators.VariadicNode'> True
所以检查 ¬25 in (¬25)
实际上确实 return 正确。
谁能告诉我这是怎么回事?
如果需要更多信息,其余代码可在 [deleted] 获得(希望它是公开的,我是 github 的新手,所以我不知道他们的政策)。请注意,它仍然是一个 WIP。
类 位于 operators.py
中,其余(相关)代码在 SAT_solver.py
中,而 test.py
允许简单的 运行整个项目的 ning,前提是安装了 networkx 库。
编辑
我现在已将示例 dimacs .txt 文件推送到 github 存储库,该存储库导致了所描述的问题。只需将 hamilton.txt
、SAT_solver.py
和 operators.py
从存储库下载到同一个文件夹,运行 SAT_solver.py
(它有一个 main() 方法)并输入 hamilton
或 hamilton.txt
当提示输入问题文件名时到命令行(提示时将解决方案文件名留空以防止程序写入任何文件)。这应该会产生大量输出,包括如上所述的有问题的行。
您发布的代码不是您 github 存储库中的代码。您的代码有 UnaryNode.__eq__
个
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
repo 代码有
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.op is other.op and self.child == other.child
这还要求运算符相同。检测您的代码并在失败时中断表明您在某处生成了两个不同的 NOT 运算符:
>>> str(x)
'¬24'
>>> str(l)
'¬24'
>>> x == l
False
>>> x.child == l.child
True
>>> str(x.op)
'¬'
>>> str(l.op)
'¬'
>>> x.op == l.op
False
>>> id(x.op)
2975964300
>>> id(l.op)
2976527276
追踪问题发生的位置并按照您喜欢的方式进行修复,无论是避免超过一个还是不关心是否超过一个(我的偏好)。我知道在一条已删除的评论中你写了 "The operators are single objects and I want them to be compared for equality based on their references",但是 (1) 它们不是单个对象,并且 (2) 如果你不想要这样一个不必要的东西,你就不会遇到麻烦了..
如果我不得不猜测,当您调用 copy.deepcopy
时会引入其他运算符,但您肯定不会使用单例。
我有以下自定义 类(精简了一些),实现了一个表达式树:
from abc import ABC, abstractmethod
class Operator:
def __init__(self, str_reps):
self.str_reps = str_reps
def __str__(self):
return self.str_reps[0]
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __hash__(self):
return hash(str(self))
NOT = Operator(["¬", "~", "not", "!"])
AND = Operator(["∧", "&", "and"])
OR = Operator(["∨", "|", "or"])
class Node(ABC):
@abstractmethod
def __eq__(self, other):
pass
@abstractmethod
def __hash__(self):
pass
def __ne__(self, other):
return not self == other
@abstractmethod
def __str__(self):
pass
@abstractmethod
def __invert__(self):
pass
def bracket_if_necessary(self):
return str(self)
class Leaf(Node):
def __init__(self, v):
self.val = v
def __eq__(self, other):
if not isinstance(other, Leaf):
return False
return self.val == other.val
def __hash__(self):
return hash(self.val)
def __str__(self):
return str(self.val)
def __invert__(self):
return UnaryNode(self)
class UnaryNode(Node):
def __init__(self, child):
self.child = child
self.hash = hash(NOT) + hash(self.child)
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
def __hash__(self):
return self.hash
def __str__(self):
return str(NOT) + self.child.bracket_if_necessary()
def __invert__(self):
return self.child
class VariadicNode(Node):
def __init__(self, op, children):
self.op = op
self.children = children
self.hash = hash(self.op) + sum(hash(child) for child in self.children)
def __eq__(self, other):
if not isinstance(other, VariadicNode):
return False
return self.op is other.op and set(self.children) == set(other.children)
def __hash__(self):
return self.hash
def __str__(self):
return (" " + str(self.op) + " ").join(child.bracket_if_necessary() for child in self)
def __invert__(self):
return VariadicNode(AND if self.op is OR else OR, tuple(~c for c in self))
def bracket_if_necessary(self):
return "(" + str(self) + ")"
def __iter__(self):
return iter(self.children)
def __contains__(self, item):
return item in self.children
如果我 运行 这样做并尝试
Leaf("36") == Leaf("36)
~Leaf("36") == ~Leaf("36")
~~Leaf("36") == Leaf("36")
他们都return如预期的那样。
但是,我运行正在研究利用这些节点的代码中的错误:
# Simplify procedure in DPLL Algorithm
def _simplify(cnf, l):
# print for debugging
for c in cnf:
print("b", l, ~l, type(l), "B", c, type(c), l in c)
return VariadicNode(AND, tuple(_filter(c, ~l) for c in cnf if l not in c))
# Removes the chosen unit literal (negated above) from clause c
def _filter(c, l):
# print for debugging
for x in c:
print("a", l, type(l), "A", x, type(x), x==l)
return VariadicNode(c.op, tuple(x for x in c if x != l))
此处 cnf
作为 VariadicNode(AND)
给出,所有 children 为 VariadicNode(OR)
。 VariadicNode
的 Children 总是作为元组给出。
这两个打印结果如下:
a ¬25 <class 'operators.UnaryNode'> A ¬25 <class 'operators.UnaryNode'> False
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 ∨ ¬36 <class 'operators.VariadicNode'> False
这不应该发生(第一行的 ¬25 == ¬25
和第二行的 ¬25 in (¬25 ∨ ¬36)
都应该 return True)。但是输出中还有一行:
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 <class 'operators.VariadicNode'> True
所以检查 ¬25 in (¬25)
实际上确实 return 正确。
谁能告诉我这是怎么回事?
如果需要更多信息,其余代码可在 [deleted] 获得(希望它是公开的,我是 github 的新手,所以我不知道他们的政策)。请注意,它仍然是一个 WIP。
类 位于 operators.py
中,其余(相关)代码在 SAT_solver.py
中,而 test.py
允许简单的 运行整个项目的 ning,前提是安装了 networkx 库。
编辑
我现在已将示例 dimacs .txt 文件推送到 github 存储库,该存储库导致了所描述的问题。只需将 hamilton.txt
、SAT_solver.py
和 operators.py
从存储库下载到同一个文件夹,运行 SAT_solver.py
(它有一个 main() 方法)并输入 hamilton
或 hamilton.txt
当提示输入问题文件名时到命令行(提示时将解决方案文件名留空以防止程序写入任何文件)。这应该会产生大量输出,包括如上所述的有问题的行。
您发布的代码不是您 github 存储库中的代码。您的代码有 UnaryNode.__eq__
个
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
repo 代码有
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.op is other.op and self.child == other.child
这还要求运算符相同。检测您的代码并在失败时中断表明您在某处生成了两个不同的 NOT 运算符:
>>> str(x)
'¬24'
>>> str(l)
'¬24'
>>> x == l
False
>>> x.child == l.child
True
>>> str(x.op)
'¬'
>>> str(l.op)
'¬'
>>> x.op == l.op
False
>>> id(x.op)
2975964300
>>> id(l.op)
2976527276
追踪问题发生的位置并按照您喜欢的方式进行修复,无论是避免超过一个还是不关心是否超过一个(我的偏好)。我知道在一条已删除的评论中你写了 "The operators are single objects and I want them to be compared for equality based on their references",但是 (1) 它们不是单个对象,并且 (2) 如果你不想要这样一个不必要的东西,你就不会遇到麻烦了..
如果我不得不猜测,当您调用 copy.deepcopy
时会引入其他运算符,但您肯定不会使用单例。