Это связано с тем, как вы построили свой объект списка:
x = [1] * 1000
Это создает список только с одним объектом, на который ссылаются 1000 раз; умножение списка не создает копии значения. Чтобы понять, почему это важно, нам нужно посмотреть, как Python списки выполняют подсчет.
list.count()
l oop может выглядеть следующим образом, быстрый Python перевод реализации написано в C:
def count(self, value):
count = 0
for elem in self:
if elem == value:
count += 1
return count
Это достаточно просто, верно? Однако это не совсем так; фактический код использует PyObject_RichCompareBool()
, что первые тесты для идентификации объекта . Это действительно:
if elem is value or elem == value:
Идентификационное тестирование (простой тест на равенство указателей) выполняется намного быстрее, когда все ваши элементы списка являются одним и тем же объектом :
>>> import random
>>> v = random.randint(1000, 100000000)
>>> x = [v] * 1000
>>> all(value is v for value in x)
True
Вы можете воспроизвести это с любым случайным значением:
>>> from timeit import Timer
min(Timer('y=x.count(v)',setup='import random; v = random.randint(1000, 10000000); x=[v] * 1000').repeat(number=100000))
0.2716284029884264
>>> min(Timer('y=x.count(w)',setup='import random; v = random.randint(1000, 10000000); x=[v] * 1000; w = v + 1').repeat(number=100000))
1.0827720829984173
Простое сравнение указателей перед проверкой на равенство значений имеет смысл, как показывают эти числа. Именно поэтому реализация Python интернирует некоторые часто используемые значения, такие как маленькие целые числа ( от -5 до 256, включительно ) или строковые значения, которые также являются допустимыми Python идентификаторами.
Это не сработало бы, если бы вы не использовали здесь маленькое целое число в качестве аргумента x.count()
; потому что 1
интернирован, что x.count(1)
использует объект, который также является членом списка; x[0] is 1
верно.