Python 巨蟒;在;语句搜索对象列表

Python 巨蟒;在;语句搜索对象列表,python,list,search,python-2.7,Python,List,Search,Python 2.7,我希望有人能解释为什么搜索对象引用列表比搜索普通列表慢得多。这是使用python“in”关键字来搜索,我认为它以“C编译器”的速度运行。我认为列表只是对象引用(指针)的数组,所以搜索应该非常快。两个列表在内存中正好是412236字节 正常列表(搜索需要0.000秒): 对象引用列表(搜索需要0.469!!秒): 编辑:显然这与旧样式类的开销比新样式类大有关。我的脚本只有400个对象,现在只需让所有类从“object”类继承,就可以轻松处理多达10000个对象。就在我以为我懂Python的时候

我希望有人能解释为什么搜索对象引用列表比搜索普通列表慢得多。这是使用python“in”关键字来搜索,我认为它以“C编译器”的速度运行。我认为列表只是对象引用(指针)的数组,所以搜索应该非常快。两个列表在内存中正好是412236字节

正常列表(搜索需要0.000秒):

对象引用列表(搜索需要0.469!!秒):


编辑:显然这与旧样式类的开销比新样式类大有关。我的脚本只有400个对象,现在只需让所有类从“object”类继承,就可以轻松处理多达10000个对象。就在我以为我懂Python的时候

我以前读过关于新样式和旧样式的比较,但从来没有提到旧样式的类比新样式的类慢100倍。搜索特定实例的对象实例列表的最佳方法是什么?
1.继续使用“in”语句,但确保所有类都是新样式。
2.使用“is”语句执行其他类型的搜索,如:

[obj for obj in spamlist if obj is target]

三,。还有其他更类似Python的方法吗?

Python创建一个不可变的“a”对象,列表中的每个元素都指向同一个对象。由于Spam()是可变的,所以每个实例都是不同的对象,取消对spamlist中指针的引用将访问RAM中的许多区域。性能差异可能与硬件缓存命中/未命中有关

显然,如果在结果中包含列表创建时间(而不仅仅是spamlist中的
Spam()
),那么性能差异会更大。也可以尝试spamlist中的
x=Spam();x
,看看这是否有区别


我很好奇
any(imap(equalsFunc,spamlist))
如何比较。

Python创建了一个不可变的“a”对象,列表中的每个元素都指向同一个对象是可变的,每个实例都是不同的对象,在spamlist中取消对指针的引用将访问RAM中的许多区域。性能差异可能与硬件缓存命中/未命中有关

显然,如果在结果中包含列表创建时间(而不仅仅是spamlist中的
Spam()
),那么性能差异会更大。也可以尝试spamlist中的
x=Spam();x
,看看这是否有区别


我很好奇
any(imap(equalsFunc,spamlist))
如何比较。

这主要是因为旧式类的特殊方法查找机制不同

正如您所看到的,
Spam
object
继承的版本运行速度要快得多,几乎与字符串的运行速度一样快

列表中的
操作符使用
==
来比较项目是否相等。
=
被定义为按该顺序尝试对象的
\uuuuuueq\uuu
方法、它们的
\ucmp\uuuu
方法和指针比较

对于旧式类,这是以一种简单但缓慢的方式实现的。Python实际上必须在每个实例的dict以及每个实例的类和超类的dict中查找
\uuuuuu eq\uuuu
\uuu cmp\uuuuuu
方法。
\uuuuuuuuu强制
也会被查找,作为三方比较过程的一部分。当这些方法实际上是存在的,这就像12个dict查找,只是为了进行指针比较。除了dict查找之外,还有很多其他开销,我不确定该过程的哪些方面最耗时,但可以说该过程比可能的更昂贵

对于内置类型和新样式类,情况更好。首先,Python不在实例的dict上查找特殊方法。这可以节省一些dict查找并启用下一部分。其次,类型对象具有与Python级特殊方法相对应的C级函数指针。当用C实现或不使用特殊方法时对应的函数指针允许Python完全跳过方法查找过程。这意味着在新样式的情况下,Python可以快速检测到它应该直接跳到指针比较

至于您应该做什么,我建议在
和新型类中使用
。如果您发现此操作正在成为一个瓶颈,但您需要旧式类来实现向后兼容性,
any(x是y代表y在l中)
的运行速度大约是
x在l中的20倍:

>>> timeit.timeit('x in l', '''
... class Foo: pass
... x = Foo(); l = [Foo()] * 100000''', number=10)
2.8618816054721936
>>> timeit.timeit('any(x is y for y in l)', '''
... class Foo: pass
... x = Foo(); l = [Foo()] * 100000''', number=10)
0.12331640524583776

这主要是由于旧式类的特殊方法查找机制不同

正如您所看到的,
Spam
object
继承的版本运行速度要快得多,几乎与字符串的运行速度一样快

列表中的
操作符使用
==
来比较项目是否相等。
=
被定义为按该顺序尝试对象的
\uuuuuueq\uuu
方法、它们的
\ucmp\uuuu
方法和指针比较

对于旧式类,这是以一种简单但缓慢的方式实现的。Python实际上必须在每个实例的dict以及每个实例的类和超类的dict中查找
\uuuuuu eq\uuuu
\uuu cmp\uuuuuu
方法。
\uuuuuuuuu强制
也会被查找,作为三方比较过程的一部分。当这些方法实际上是存在的,这就像12个dict查找,只是为了进行指针比较。除了dict查找之外,还有很多其他开销,我不确定该过程的哪些方面最耗时,但可以说该过程比可能的更昂贵

对于内置类型和新样式的类,情况会更好。首先,Python不会在实例的dict上寻找特殊的方法。这可以节省som
[obj for obj in spamlist if obj is target]
>>> timeit.timeit("Spam() in l", """
... # Old-style
... class Spam: pass
... l = [Spam() for i in xrange(100000)]""", number=10)
3.0454677856675403
>>> timeit.timeit("Spam() in l", """
... # New-style
... class Spam(object): pass
... l = [Spam() for i in xrange(100000)]""", number=10)
0.05137817007346257
>>> timeit.timeit("'a' in l", 'l = ["b" for i in xrange(100000)]', number=10)
0.03013876870841159
>>> timeit.timeit('x in l', '''
... class Foo: pass
... x = Foo(); l = [Foo()] * 100000''', number=10)
2.8618816054721936
>>> timeit.timeit('any(x is y for y in l)', '''
... class Foo: pass
... x = Foo(); l = [Foo()] * 100000''', number=10)
0.12331640524583776
>>> alist=['a' for x in range(100000)]
>>> len(alist)
100000
>>> len({id(x) for x in alist})
1
>>> olist=[object() for x in range(100000)]
>>> len(olist)
100000
>>> len({id(x) for x in olist})
100000
>>> import dis
>>> def f(x, y):
...   return x in y
...
>>> dis.dis(f)
2          0 LOAD_FAST                0 (x)
           3 LOAD_FAST                1 (y)
           6 COMPARE_OP               6 (in)
           9 RETURN_VALUE