当前位置: 首页 > 面试题库 >

使用条件生成器表达式的意外行为

计向晨
2023-03-14
问题内容

这个问题已经在这里有了答案

生成器表达式使用生成器创建后分配的列表 (5个答案)

去年关闭。

我正在运行一段代码,该代码在程序的某个部分意外出了逻辑错误。在研究本节时,我创建了一个测试文件来测试正在运行的语句集,并发现了一个看起来很奇怪的异常错误。

我测试了以下简单代码:

array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original to something else

print(list(f)) # Outputs filtered

输出为:

>>> []

是的,什么都没有。我期望过滤器理解能获得2中的项并输出,但是我没有得到:

# Expected output
>>> [2, 2]

当我注释掉第三行再次对其进行测试时:

array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
### array = [5, 6, 1, 2, 9] # Ignore line

print(list(f)) # Outputs filtered

输出正确(您可以自己测试):

>>> [2, 2]

有一次我输出了变量的类型f

array = [1, 2, 2, 4, 5] # Original array
f = (x for x in array if array.count(x) == 2) # Filters original
array = [5, 6, 1, 2, 9] # Updates original

print(type(f))
print(list(f)) # Outputs filtered

我得到:

>>> <class 'generator'>
>>> []

为什么在Python中更新列表会更改另一个生成器变量的输出?我觉得这很奇怪。


问题答案:

Python的生成器表达式是后期绑定(请参阅PEP
289-生成器表达式
)(其他答案称为“惰性”):

早期绑定与后期绑定

经过大量讨论,我们决定应立即评估[生成器表达式]的第一个(最外部)表达式,并在执行生成器时评估其余表达式。

[…] Python对lambda表达式采用后期绑定方法,并且没有自动早期绑定的先例。有人认为,引入新的范例将不必要地引入复杂性。

在探索了许多可能性之后,出现了一个共识,即绑定问题难以理解,应大力鼓励用户在函数中使用生成器表达式,这些函数立即使用其参数。对于更复杂的应用程序,完整的生成器定义在范围,生存期和绑定方面显而易见,因此始终是上乘的。

这意味着它 for在创建生成器表达式 时才 评估最外层。因此,它实际上 值与array“子表达式”中的名称in array绑定(实际上,此时绑定了与之等效的iter(array)值)。但是,当您遍历生成器时,if array.count调用实际上是指当前命名的array

由于实际上list不是a,因此array我将答案的其余部分中的变量名称更改为更准确。

在您的第一种情况下,list您进行迭代,而list您所计数的将有所不同。就好像您使用了:

list1 = [1, 2, 2, 4, 5]
list2 = [5, 6, 1, 2, 9]
f = (x for x in list1 if list2.count(x) == 2)

因此,您要检查每个元素中list1是否list2包含两个元素。

您可以通过修改第二个列表轻松地验证这一点:

>>> lst = [1, 2, 2]
>>> f = (x for x in lst if lst.count(x) == 2)
>>> lst = [1, 1, 2]
>>> list(f)
[1]

如果在第一个列表上进行迭代并计入第一个列表中,它将返回[2, 2](因为第一个列表包含两个2)。如果迭代并计入第二个列表,则输出应为[1, 1]。但是由于迭代了第一个列表(包含一个1),但检查了第二个列表(包含两个1),因此输出只是一个1

使用生成器函数的解决方案

有几种可能的解决方案,如果不立即对其进行迭代,我通常不希望使用“生成器表达式”。一个简单的生成器函数足以使其正常工作:

def keep_only_duplicated_items(lst):
    for item in lst:
        if lst.count(item) == 2:
            yield item

然后像这样使用它:

lst = [1, 2, 2, 4, 5]
f = keep_only_duplicated_items(lst)
lst = [5, 6, 1, 2, 9]

>>> list(f)
[2, 2]

请注意,PEP(请参见上面的链接)还指出,对于更复杂的事情,最好使用完整的生成器定义。

使用带有计数器的生成器功能的更好解决方案

更好的解决方案(避免遍历二次运行时的行为,因为您遍历了整个数组中的每个元素)将对collections.Counter元素计数一次,然后在恒定时间内进行查找(导致线性时间):

from collections import Counter

def keep_only_duplicated_items(lst):
    cnts = Counter(lst)
    for item in lst:
        if cnts[item] == 2:
            yield item

附录:使用子类“可视化”发生的情况以及发生的时间

创建一个list在调用特定方法时可以打印的子类非常容易,因此可以验证它确实可以那样工作。

在这种情况下,我只是重写方法__iter__count因为我对生成器表达式要在哪个列表中进行迭代以及在哪个列表中计数感兴趣。方法主体实际上只是委托给超类并打印一些内容(因为它使用时super没有参数和f字符串,因此它需要Python
3.6,但应该很容易适应其他Python版本):

class MyList(list):
    def __iter__(self):
        print(f'__iter__() called on {self!r}')
        return super().__iter__()

    def count(self, item):
        cnt = super().count(item)
        print(f'count({item!r}) called on {self!r}, result: {cnt}')
        return cnt

这是一个简单的子类,仅在调用__iter__count方法时进行打印:

>>> lst = MyList([1, 2, 2, 4, 5])

>>> f = (x for x in lst if lst.count(x) == 2)
__iter__() called on [1, 2, 2, 4, 5]

>>> lst = MyList([5, 6, 1, 2, 9])

>>> print(list(f))
count(1) called on [5, 6, 1, 2, 9], result: 1
count(2) called on [5, 6, 1, 2, 9], result: 1
count(2) called on [5, 6, 1, 2, 9], result: 1
count(4) called on [5, 6, 1, 2, 9], result: 0
count(5) called on [5, 6, 1, 2, 9], result: 1
[]


 类似资料:
  • Quartz 的Cron任务调度表达式一般人很难理解,在Googole上查询也没有发现类似的代码,所以开发了一个对Quartz Cron 表达式的可视化双向解析和生成的一个java的GUI程序,供使用Quartz的程序员参考和使用,源代码放在SourceForge网站

  • 问题内容: 在这里看到讨论之后:Python-产生时差,我很好奇。我最初还以为生成器比列表快,但是当涉及sorted()时我不知道。将生成器表达式发送到sorted()而不是列表有什么好处吗?生成器表达式最终是否仍要在sorted()内放入列表中? 编辑:我只能接受一个答案让我感到悲伤,因为我感到很多答复都有助于澄清这个问题。再次感谢大家。 问题答案: 首先要做的是将数据转换为列表。基本上,实现的

  • 我想当我尝试在 Kotlin 中编写一行代码时我搞砸了,似乎没有问题,但 IntelliJ 在这里给了我这个错误:

  • New in Django 1.8. 条件表达式允许你在过滤器、注解、聚合和更新操作中使用 if ... elif ... else的逻辑。条件表达式为表中的每一行计算一系列的条件,并且返回匹配到的结果表达式。条件表达式也可以像其它 表达式一样混合和嵌套。 条件表达式类 我们会在后面的例子中使用下面的模型: from django.db import models class Client(m

  • 问题内容: 谁能帮助我了解为什么以下Python脚本会返回? 我想这与零长度实体有关,但不能完全理解。 问题答案: 除非 序列中存在元素, 否则 始终返回。 您的循环产生0个项目,因此返回。 这是记录: 返回如果的所有元素 迭代 是真实的( 或者,如果可迭代为空 )。 强调我的。 同样,将始终返回, 除非 序列中的元素为,所以对于空序列,将返回默认值:

  • 本文向大家介绍python 中的列表生成式、生成器表达式、模块导入,包括了python 中的列表生成式、生成器表达式、模块导入的使用技巧和注意事项,需要的朋友参考一下 5.16 列表生成式 5.17 列表生成式与生成器表达式的应用 第六章模块 什么是模块? 模块就是一系统功能的集合体,在python中,一个py文件就是一个模块,比如module.py,其中模块名module 6.1 import