我有两个类,结构和片段,代表化学结构。 如果我比较同一类的两个实例,比较应该正常进行,但是如果我比较一个结构和一个片段,比较应该基于唯一标识化学结构的字符串属性
不幸的是,在我的尝试中,运行时间急剧增加(使用集合和字典的操作)
班级结构:
def __eq__(self, other):
if isinstance(other, Structure):
return self is other
else:
return self.cansmiles is other.cansmiles_unstarred
def __hash__(self):
return hash(self.cansmiles)
片段eq和hash使用相同的逻辑实现
“cansmiles”字符串可能相当长(通常为25,最多为100),我试图在没有运气的情况下将其切碎
问题是:什么是内置的散列和eq如此高效的魔力?有没有办法有效地覆盖它们?有什么建议吗
更新:我可以避免每次调用__hash__
时都计算哈希值吗
就像在评论中提到的,你有一个更大的问题:你的代码有bug。虽然
is
可能稍微快一点,但它也做了错误的事情虽然一些快速测试可能会让您相信它是有效的—因为Python优化了一些简单的案例—但这些示例表明它通常不起作用:
至于
__eq__()
实现,如果您尝试比较Structure
和Fragment
之外的任何东西,它就会崩溃(因为其他对象可能没有cansmiles
/cansmiles_unstarred
属性,给出一个AttributeError
-或者如果它们有,代码会做错误的事情,因为您可能不想与随机未知对象进行比较)现在,考虑一下代码的这一部分:
因为
self
是一个Structure
,如果self
与other
是同一个对象,那么另一个显然也是一个Structure
,所以if
有点不必要。如果你同时考虑这两个问题,你会发现整个问题都是错误的:你不需要检查is
的类型,但是你需要检查字符串检查的类型!因此:现在,至于这一变化将如何影响字典查找的速度:它不会。Python通过首先检查
is
,然后返回到==
来优化字典查找。因此,如果使用与保存数据相同的类型执行查找,因为同一类型中的相等性是基于标识的,因此实际调用__eq__()
的唯一时间是发生哈希冲突(很少)但是,如果使用另一种类型进行查找,对象将不同,因此它将返回到
==
并调用__eq__()
-但是从is
到==
的更改仍然不会影响速度:==
可能比is
稍慢,但是函数调用要慢一个数量级,因此isinstance()
所花费的时间将使两种比较所花费的时间相形见绌那么为什么内置的
__eq__()
和__hash__()
要快得多呢?那么,为什么sum(l)
比for v in l: t += v
快呢?因为运行Python字节码比运行本机代码慢得多。在评论中,您说过is
只检查指针-但这不是真的,是吗?您必须从字节码中获取下一个操作码及其参数,选择正确的处理程序,跳转到处理程序,等等如果你说
def __eq__(self, other): return self is other
,并调用该函数,而不是“仅仅做一个指针比较”,你必须设置一个执行框架,为本地作用域填充一个字典,对字典进行两次查找以根据名称获取值,将它们推到堆栈中,从堆栈中弹出值,进行比较,将结果推送到堆栈上,从堆栈中弹出结果,最后返回值——每一步包括获取和解码字节码、跳转等等。所以是的,这将比内置的__eq__()
慢,它可能实际上非常接近“指针比较”至于避免重新计算散列:你已经这样做了——你正在对字符串进行散列,字符串缓存它们的散列,所以它们不会被重新计算。内置的
__hash__()
基于对象的id,因此它应该是常量时间,因此它显然比计算字符串的哈希(应该是线性时间)要快。但是,即使重新计算字符串散列,这也是在本机代码中完成的,因此速度差异将很小——并且与定义自定义字节码时从本机代码切换到运行python字节码这一事实相形见绌当然,您可以自己缓存
hash()
返回的值-您需要添加一个if来检查哈希是否已经计算过,但是由于调用函数的速度相对较慢,即使添加了代码,结果也可能会稍微快一些。但是,您可能会添加代码,并降低可读性,以获得非常小的速度提升。如果你想要速度,w你为什么不直接用字符串作为键呢?或者,如果速度真的很重要,你可能想看看PyPy(甚至是Numba)相关问题 更多 >
编程相关推荐