Подтвердить что ты не робот

Кортеж или список при использовании 'in' в предложении 'if'?

Какой подход лучше? Используя кортеж, например:

if number in (1, 2):

или список, например:

if number in [1, 2]:

Какой из них рекомендуется для такого использования и почему (как логичный, так и эффективный)?

4b9b3361

Ответ 1

Интерпретатор CPython заменяет вторую форму первой.

Это потому, что загрузка кортежа из константы - одна операция, но список будет 3 операции; загрузите два целочисленных содержимого и создайте новый объект списка.

Поскольку вы используете литерал списка, который недоступен другим, он заменяет кортеж:

>>> import dis
>>> dis.dis(compile('number in [1, 2]', '<stdin>', 'eval'))
  1           0 LOAD_NAME                0 (number)
              3 LOAD_CONST               2 ((1, 2))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE        

Здесь второй байт-код загружает кортеж (1, 2) как константу за один шаг. Сравните это с созданием объекта списка, не используемого в тесте на членство:

>>> dis.dis(compile('[1, 2]', '<stdin>', 'eval'))
  1           0 LOAD_CONST               0 (1)
              3 LOAD_CONST               1 (2)
              6 BUILD_LIST               2
              9 RETURN_VALUE        

Здесь для объекта списка длиной N требуется N + 1 шагов.

Эта подстановка представляет собой оптимизацию Peephole, специфичную для CPython; см. Python/peephole.c источник. Для других реализаций Python, тогда вы хотите использовать неизменяемые объекты.

Тем не менее, лучший вариант при использовании Python 3.2 и выше - использовать набор литералов:

if number in {1, 2}:

поскольку оптимизатор подзаголовков заменит это на frozenset() объект и тесты на членство в наборах, это O (1) постоянные операции:

>>> dis.dis(compile('number in {1, 2}', '<stdin>', 'eval'))
  1           0 LOAD_NAME                0 (number)
              3 LOAD_CONST               2 (frozenset({1, 2}))
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE

Эта оптимизация была добавлена ​​в Python 3.2, но не была обращена к Python 2.

Таким образом, оптимизатор Python 2 не распознает эту опцию, и стоимость создания либо set, либо frozenset из содержимого почти гарантированно будет более дорогостоящей, чем использование кортежа для теста.

Установить тесты на членство - O (1) и fast; тестирование против кортежа - это O (n) худший случай. Несмотря на то, что при повторном тестировании набор должен вычислять хеш (более высокая постоянная стоимость, кэшированная для неизменных tupes), стоимость тестирования против кортежа, отличного от первого элемента, всегда будет выше. Поэтому в среднем, наборы быстрее:

>>> import timeit
>>> timeit.timeit('1 in (1, 3, 5)', number=10**7)  # best-case for tuples
0.21154764899984002
>>> timeit.timeit('8 in (1, 3, 5)', number=10**7)  # worst-case for tuples
0.5670104179880582
>>> timeit.timeit('1 in {1, 3, 5}', number=10**7)  # average-case for sets
0.2663505630043801
>>> timeit.timeit('8 in {1, 3, 5}', number=10**7)  # worst-case for sets
0.25939063701662235