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

使用条件生成器表达式的意外行为,第1张

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

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

早期绑定与后期绑定

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

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

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

这意味着它

for
在创建生成器表达式 时才 评估最外层。因此,它实际上 值与
array
“子表达式”中的名称
inarray
绑定(实际上,此时绑定了与之等效的
iter(array)
值)。但是,当您遍历生成器时,
ifarray.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 Counterdef 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: 1count(2) called on [5, 6, 1, 2, 9], result: 1count(2) called on [5, 6, 1, 2, 9], result: 1count(4) called on [5, 6, 1, 2, 9], result: 0count(5) called on [5, 6, 1, 2, 9], result: 1[]


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5651028.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存