Есть ли сценарий, в котором `foo in list(bar)` нельзя заменить на `foo in bar`?

Я копаюсь в базе кода, содержащей тысячи вхождений foo in list(bar), например:

  • как логическое выражение:
if foo in list(bar) or ...:
   ...
  • в цикле for:
for foo in list(bar):
    ...
  • в выражении генератора:
",".join(str(foo) for foo in list(bar))

Существует ли сценарий (например, заданная версия Python, известное поведение средства проверки типов и т. д.), в котором foo in list(bar) не является просто версией foo in bar, требующей больших затрат памяти? Что мне здесь не хватает?

Зависит от того, что такое bar на самом деле. Поведение in для объектов класса определяется тем, как он переопределяет __contains__. Возможно, bar.__contains__(foo) не эквивалентно list(bar).__contains__(foo)

Chris 04.09.2024 22:23

Разве не в этом вся суть вопроса? Если вы, ребята, собираетесь сказать «это зависит», то есть ли у вас в голове конкретный сценарий?

Michael Cao 04.09.2024 22:25

Вы пытались выяснить или спросить причины в имеющемся у вас коде?

no comment 05.09.2024 00:36

Мне это интересно. Ответы показывают некоторые причины для вызова list туда, но я думаю, что это все еще редкая необходимость, поэтому тысячи случаев все еще удивляют.

no comment 05.09.2024 05:37
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
4
86
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вот это имеет значение:

bar = iter('bar')
foo = 'b'
if foo in list(bar):
    print(*bar)

Это печатает пустую строку. При вашей замене вместо этого печатается a r.

Попробуйте это онлайн!

Итак, любой может создать тип, который работает следующим образом:

>>> class Crazy:
...     def __iter__(self):
...         yield from range(10)
...
>>> 3 in Crazy()
True
>>> class Crazy:
...     def __iter__(self): yield from range(10)
...     def __contains__(self, item): raise TypeError("sorry, cant do that")
...
>>> 3 in Crazy()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __contains__
TypeError: sorry, cant do that
>>> 3 in list(Crazy())
True
>>>

Большинство встроенных типов «хорошо себя ведут». Однако одним важным исключением, вероятно, являются str и другие подобные типы (bytes, bytearray):

>>> 'foo' in 'foobar'
True
>>> 'foo' in list('foobar')
False

Кроме того, по крайней мере, для одной популярной библиотекиnumpy это не так:

>>> import numpy as np
>>> arr = np.array([[1,2,3], [4,5,6]])
>>> arr
array([[1, 2, 3],
       [4, 5, 6]])
>>> 6 in arr
True
>>> 6 in list(arr)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
>>>

Учитывая их ситуацию, я думаю, что в примере с NumPy все наоборот. Поскольку 6 in list(arr) не работает, вероятно, у них это не тот случай. Это не показывает, что удаление list() может быть проблематичным, оно показывает, что его добавление может быть проблематичным. (Другой пример: 3 in list(itertools.count())... Я решил не публиковать это из-за отсталости.)

no comment 04.09.2024 23:04

@wjandrea пример str, изначально я редактировал его, чтобы добавить в конец ответа, но переместил его в середину и забыл это ошибочное предложение. Спасибо.

juanpa.arrivillaga 04.09.2024 23:30

@nocomment, это хороший момент, но я просто хотел подчеркнуть, что сторонние библиотеки не обязаны обеспечивать согласованность

juanpa.arrivillaga 04.09.2024 23:31
Ответ принят как подходящий

Я иногда делал/видел это, когда bar изменялся в цикле, например:

bar = {1, 2, 3}
for foo in list(bar):
    bar.add(foo + 1)

С вашей заменой это поднимает RuntimeError: Set changed size during iteration.

Попробуйте это онлайн!

Пример из стандартной библиотеки Python

    for k in list(_config_vars):
        if k.startswith(_INITPRE):
            del _config_vars[k]

Еще десятки (многие сделали по вышеуказанной причине, но не все).

Не является ли это скорее побочным эффектом использования pop, тогда как вопрос был о начальной итерации?

OneCricketeer 04.09.2024 22:40

@OneCricketeer Не уверен, что вы имеете в виду, говоря о начальной итерации. Я думаю, они спрашивают, безопасно ли удалять вызовы из списка. И это метод, который я действительно использовал/видел, и я думаю, что это может быть то, что у них есть в их коде. И удалять его там небезопасно.

no comment 04.09.2024 22:48

это, вероятно, наиболее вероятный пример, в котором вы увидите действительный вариант использования for x in list(something)

juanpa.arrivillaga 05.09.2024 00:35

Если foo и bar являются строками, тесты на членство ведут себя по-другому. <str> in <str> проверяет подстроки, а <str> in list(<str>) проверяет символы.

>>> foo = 'ab'
>>> bar = 'abc'
>>> foo in bar  # Substring
True
>>> foo in list(bar)  # Element (character)
False

Что касается циклов, не позволяйте синтаксису ввести вас в заблуждение: это не то же самое, что логическое выражение. В цикле используется итерируемый протокол (например, __iter__), а в логическом выражении используется протокол проверки членства (например, __contains__, или оно возвращается к итерируемому протоколу).

Когда bar является генератором, bar и list(bar) могут иметь и другие различия. Вот один из примеров:

def abc():
    yield 'a'
    yield 'b'
    yield 'c'
    raise Exception('Incorrect data')

foo = 'a'

if foo in abc():
    print('without list()')     # <-- This is printed (`raise` is not reached)

if foo in list(abc()):          # <-- This raises an exception
    print('with list()')        # <-- So this is never reached

В первом случае if foo in abc() не истощает генератор. Из-за ленивой оценки, как только будет получен 'a', генератор больше не будет выдавать элементы.

Во втором случае if foo in list(abc()) исчерпывает генератор, что вызывает исключение. Поэтому 'with list()' никогда не печатается.

Какой смысл or False?

wjandrea 04.09.2024 22:59

Я думаю, что исключение делает этот пример неоптимальным, поскольку это означает, что в их коде, скорее всего, нет такого случая.

no comment 04.09.2024 23:09

(Другими словами: те же рассуждения, что и я прокомментировал под ответом Хуанпы)

no comment 04.09.2024 23:10

Другие вопросы по теме