我正在研究包含数千个出现的代码库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?我在这里遗漏了什么?

4

  • 6
    取决于bar实际情况in。类对象的行为由其如何覆盖来定义__contains__。可能bar.__contains__(foo)不等同于list(bar).__contains__(foo)


    – 


  • 2
    这难道不是问题的关键吗?如果你们要说“视情况而定”,那么你们心中是否有一个具体的场景?


    – 

  • 您是否尝试找出或询问您拥有的代码中的原因?


    – 

  • 我对此很好奇。答案显示了调用list那里的一些原因,但我认为这仍然是一种罕见的需求,因此数千次发生仍然令人惊讶。


    – 



5 个回答
5

我有时会做/看到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]

(很多是出于上述原因,但不是全部)。

3

  • 这难道不是使用 pop 的副作用吗,而问题是询问初始迭代?


    – 


  • 1
    @OneCricketeer 不确定你说的初始迭代是什么意思。我认为他们问的是删除列表调用是否安全。这是我实际使用/看到过的一种技术,我认为这可能就是他们的代码中所使用的。在那里删除它是不安全的。


    – 

  • 2
    这可能是最有可能的例子,你会看到一个有效的用例for x in list(something)


    – 

这里有所不同:

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

这会打印一个空行。使用替换a r后,它会打印。

如果foobar都是字符串,成员测试的行为会有所不同。<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()'永远不会被打印。

2

  • 我认为这个例外使这个例子不再是最优的,因为这意味着他们的代码中可能没有这种情况。


    – 

  • (换句话说:与我在 juanpa 的回答下评论的理由相同)


    – 

因此,任何人都可以创建一个以这种方式工作的类型:

>>> 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和其他类似类型(bytesbytearray):

>>> '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()
>>>

2

  • 考虑到他们的情况,我认为 NumPy 示例是倒退的。既然6 in list(arr)不起作用,那可能不是他们遇到的情况。它并没有表明删除 list()可能会有问题,而是表明添加它可能有问题。(另一个例子是3 in list(itertools.count())……由于倒退,我决定不发布它。)


    – 


  • 1
    @nocomment 说得好,但我只是想强调,第三方库没有义务确保这些一致性


    –