Я копаюсь в базе кода, содержащей тысячи вхождений foo in list(bar)
, например:
if foo in list(bar) or ...:
...
for foo in list(bar):
...
",".join(str(foo) for foo in list(bar))
Существует ли сценарий (например, заданная версия Python, известное поведение средства проверки типов и т. д.), в котором foo in list(bar)
не является просто версией foo in bar
, требующей больших затрат памяти? Что мне здесь не хватает?
Разве не в этом вся суть вопроса? Если вы, ребята, собираетесь сказать «это зависит», то есть ли у вас в голове конкретный сценарий?
Вы пытались выяснить или спросить причины в имеющемся у вас коде?
Мне это интересно. Ответы показывают некоторые причины для вызова list
туда, но я думаю, что это все еще редкая необходимость, поэтому тысячи случаев все еще удивляют.
Вот это имеет значение:
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())
... Я решил не публиковать это из-за отсталости.)
@wjandrea пример str
, изначально я редактировал его, чтобы добавить в конец ответа, но переместил его в середину и забыл это ошибочное предложение. Спасибо.
@nocomment, это хороший момент, но я просто хотел подчеркнуть, что сторонние библиотеки не обязаны обеспечивать согласованность
Я иногда делал/видел это, когда 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 Не уверен, что вы имеете в виду, говоря о начальной итерации. Я думаю, они спрашивают, безопасно ли удалять вызовы из списка. И это метод, который я действительно использовал/видел, и я думаю, что это может быть то, что у них есть в их коде. И удалять его там небезопасно.
это, вероятно, наиболее вероятный пример, в котором вы увидите действительный вариант использования for x in list(something)
Если 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
?
Я думаю, что исключение делает этот пример неоптимальным, поскольку это означает, что в их коде, скорее всего, нет такого случая.
(Другими словами: те же рассуждения, что и я прокомментировал под ответом Хуанпы)
Зависит от того, что такое
bar
на самом деле. Поведениеin
для объектов класса определяется тем, как он переопределяет__contains__
. Возможно,bar.__contains__(foo)
не эквивалентноlist(bar).__contains__(foo)