结合for循环和if语句的Pythonic方式

391

我知道如何使用不同的行分别编写for循环和if语句,例如:

>>> a = [2,3,4,5,6,7,8,9,0]
... xyz = [0,12,4,6,242,7,9]
... for x in xyz:
...     if x in a:
...         print(x)
0,4,6,7,9

我知道在语句简单的情况下可以使用列表推导式来合并它们,例如:

print([x for x in xyz if x in a])

但是我找不到一个好的例子(可以参考和学习),展示在 for 循环和一些 if 语句组合之后发生的一系列复杂命令(不仅仅是“打印 x”)。我期望看到的东西像这样:

for x in xyz if x not in a:
    print(x...)

这只是Python工作方式的惯例吗?


36
就是这样,不要试图去简化已经简单的事情而使它变得过于复杂。 Pythonic 并不意味着避免每个明确的 for 循环和 if 语句。 - Felix Kling
2
你可以在for循环中使用列表推导式生成的列表。这看起来有点像你最后一个例子。 - Jacob
3
@Chewy,适当的数据结构可以加快代码速度,而不是语法糖。例如,如果a是一个列表,那么x in a会很慢。 - Nick Dandoulakis
3
这是Python,一种解释型语言;为什么会有人讨论代码有多快呢? - ArtOfWarfare
1
@ArtOfWarfare 可能是因为它被用在不应该使用的地方。在速度真正重要的地方。 - Siavoshkc
显示剩余7条评论
13个回答

447
你可以使用类似这样的生成器表达式:
gen = (x for x in xyz if x not in a)

for x in gen:
    print(x)

1
当我在for x in gen: print x中输入时,gen = (y for (x,y) in enumerate(xyz) if x not in a)会返回12。那么为什么使用enumerate时会出现意外的行为呢? - ChewyChunks
17
可以实现,但与原始的 for 和 if 块相比并不更好。 - Mike Graham
196
我很想在 Python 中使用 for x in xyz if x: 这样的语句。 - bgusach
1
你的意思是条件像“if x ** 2 <5”吗? 是的,在一行中写可能很丑陋,但是两行到底有什么问题呢?[理解/生成必须内联,因为它们是_表达式_。当您编写_语句_时,如果在循环的套件中执行复杂操作,则自然要使用行来处理它们。] 如果您担心代码缩进太多,您不必每次都使用4个空格。 for x in xyz:/_if x ** 2 <5:/____do something(/是换行符,_是空格)。 - Veky
30
对我来说,for x in (x for x in xyz if x not in a): 这个语句可以正常工作,但为什么不能直接使用 for x in xyz if x not in a: 我不确定... - Matti Wens
显示剩余14条评论

50
根据Python之禅(如果你想知道你的代码是否“Pythonic”,那就去看看吧):
  • 美丽胜于丑陋。
  • 显式胜于隐式。
  • 简单胜于复杂。
  • 扁平胜于嵌套。
  • 可读性很重要。
获取两个setsorted intersection的Pythonic方法是:
>>> sorted(set(a).intersection(xyz))
[0, 4, 6, 7, 9]

或者那些属于xyz但不属于a的元素:

>>> sorted(set(xyz).difference(a))
[12, 242]

但是对于更复杂的循环,您可能希望通过迭代一个命名良好的生成器表达式和/或调用一个命名良好的函数来展开它。试图将所有内容都放在一行上很少是“Pythonic”的。


针对您的问题和接受的答案,以下是额外的评论更新

我不确定您想要使用enumerate做什么,但如果a是一个字典,您可能想要使用键,像这样:

>>> a = {
...     2: 'Turtle Doves',
...     3: 'French Hens',
...     4: 'Colly Birds',
...     5: 'Gold Rings',
...     6: 'Geese-a-Laying',
...     7: 'Swans-a-Swimming',
...     8: 'Maids-a-Milking',
...     9: 'Ladies Dancing',
...     0: 'Camel Books',
... }
>>>
>>> xyz = [0, 12, 4, 6, 242, 7, 9]
>>>
>>> known_things = sorted(set(a.iterkeys()).intersection(xyz))
>>> unknown_things = sorted(set(xyz).difference(a.iterkeys()))
>>>
>>> for thing in known_things:
...     print 'I know about', a[thing]
...
I know about Camel Books
I know about Colly Birds
I know about Geese-a-Laying
I know about Swans-a-Swimming
I know about Ladies Dancing
>>> print '...but...'
...but...
>>>
>>> for thing in unknown_things:
...     print "I don't know what happened on the {0}th day of Christmas".format(thing)
...
I don't know what happened on the 12th day of Christmas
I don't know what happened on the 242th day of Christmas

从下面的注释中可以看出,我应该学习生成器。我从未使用过它们。谢谢。生成器比相同组合的FOR和IF语句快吗?我也使用了集合,但有时列表中的冗余元素是我不能丢弃的信息。 - ChewyChunks
@ChewyChunks:生成器并不是成为Pythonic的唯一方式! - johnsyweb
5
@Johnsyweb,如果你要引用Python之禅的话:“做一件事应该有且只有一种明显的方法。” - Wooble
@Wooble: 应该有的。我在大约同一时间内 回答另一个问题 中引用了那个部分! - johnsyweb
Python语言在Python之禅的三个原则上失败了:我不同意其他三个(显式,简单,平面)。我不是新手:它是我的主要语言已经30个月了,自2012年以来每年都使用它进行重大项目。这条评论是否与主题无关?鉴于问题中提到了Python之禅,这并不一定是无关的。 - WestCoastProjects
@Wooble 除非你是荷兰人 - acidjunk

40
下面是来自已接受答案的简化/一行总结:
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]

for x in (x for x in xyz if x not in a):
    print(x)

12
242

请注意 generator 保持了内联。这在python2.7python3.6上进行了测试(注意print中的括号 ;))

即使如此,这实际上是有点麻烦的: x 被提到了四次


25

我个人认为这是最漂亮的版本:

a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]
for x in filter(lambda w: w in a, xyz):
  print x

编辑

如果您非常希望避免使用lambda,您可以使用偏函数应用并使用operator模块(它提供了大多数运算符的功能)。

https://docs.python.org/2/library/operator.html#module-operator

from operator import contains
from functools import partial
print(list(filter(partial(contains, a), xyz)))

9
通常人们使用 lambda 表达式时,实际上只需要更简单的方式,比如使用 filter(a.__contains__, xyz) - Veky
我认为你误解了一些东西。__contains__是一个像其他方法一样的方法,只不过它是一个_特殊_方法,这意味着它可以被操作符(在这种情况下是in)间接调用。但它也可以直接调用,它是公共API的一部分。私有名称被明确定义为最多具有一个尾随下划线,以提供特殊方法名称的例外,并且它们在类作用域中词法上时会进行名称混淆。请参见https://docs.python.org/3/reference/datamodel.html#specialnames和https://docs.python.org/3.6/tutorial/classes.html#private-variables。 - Veky
当然可以,但是为了能够引用一个只使用属性就可以访问的方法而导入两个模块似乎很奇怪(当双重分派很重要时通常使用运算符,但是“in”在右操作数方面是单向分派)。此外,请注意,“operator”还将“contains”方法导出为“__contains__”名称,因此它肯定不是私有名称。我认为你只需要学会接受这个事实,即并不是每个双下划线都意味着“远离”。:-] - Veky
1
过滤器看起来更加优雅,特别是对于那些需要定义函数而不是lambda表达式的复杂条件,也许给lambda函数命名会增加一些可读性。当迭代元素是列表项的某些修改时,生成器似乎更好。 - Khanis Rok
1
@TadhgMcDonald-Jensen,你可以按照自己的喜好将代码拆分为函数,这只是示例代码。请记住,是否值得引入额外的符号取决于传递函数的复杂性与提供的符号。这取决于你是否需要文档保证和单元测试覆盖率等因素。 - Alexander Oh
显示剩余2条评论

18

我可能会使用:

for x in xyz: 
    if x not in a:
        print(x...)

@KirillTitov 是的,Python是一种基本上非函数式的语言(这是纯命令式编码),我同意这个答案的作者所说的,这就是Python编写方式的设定。尝试使用函数式会导致代码难以阅读或不符合“Pythonic”的结果。我可以在我使用的其他所有语言中进行函数式编码(Scala、Kotlin、JavaScript、R、Swift等),但在Python中却很困难/笨拙。 - WestCoastProjects

9
a = [2,3,4,5,6,7,8,9,0]
xyz = [0,12,4,6,242,7,9]  
set(a) & set(xyz)  
set([0, 9, 4, 6, 7])

非常深奥,@lazyr,但这并不能帮助我改进一个依赖于迭代一个列表并忽略另一个列表中匹配元素的复杂代码块。将第一个列表视为集合,并将其与第二个不断增长的“忽略”列表进行联合/差异比较,是否更快? - ChewyChunks
尝试这个代码: import time a = [2,3,4,5,6,7,8,9,0] xyz = [0,12,4,6,242,7,9] start = time.time() print (set(a) & set(xyz)) print time.time() - start - Kracekumar
@ChewyChunks,如果在迭代过程中任一列表发生改变,检查每个元素是否在忽略列表中可能会更快——不过你应该将其设为一个忽略集合。在集合中检查成员非常快速:if x in ignore: ... - Lauritz V. Thaulow
@lazyr 我刚刚用“忽略集”重写了我的代码,而不是使用忽略列表。看起来处理时间要慢得多。(公平地说,我是在比较 if set(a) - set(ignore) == set([]):,所以也许这就是为什么检查成员资格比较慢。将来我会在比我正在编写的更简单的示例上再次测试这个问题。) - ChewyChunks

7

我喜欢Alex的答案,因为filter正是应用于列表的if语句,所以如果你想要根据条件探索列表的子集,这似乎是最自然的方法。

mylist = [1,2,3,4,5]
another_list = [2,3,4]

wanted = lambda x:x in another_list

for x in filter(wanted, mylist):
    print(x)

这种方法适用于关注点分离,如果条件函数发生变化,唯一需要修改的代码就是函数本身。

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

for x in filter(wanted, mylist):
    print(x)

生成器(generator)方法在你不需要列表成员时更加合适,而是需要对这些成员进行修改,这似乎更适合于生成器。
mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.5 for x in mylist if wanted(x))

for x in generator:
    print(x)

此外,过滤器也可以与生成器一起使用,尽管在这种情况下效率不高。

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

generator = (x**0.9 for x in mylist)

for x in filter(wanted, generator):
    print(x)

当然,这样写还是很好的:

mylist = [1,2,3,4,5]

wanted = lambda x:(x**0.5) > 10**0.3

# for x in filter(wanted, mylist):
for x in mylist if wanted(x):
    print(x)

6

如果生成器表达式变得过于复杂,您也可以使用生成器

def gen():
    for x in xyz:
        if x in a:
            yield x

for x in gen():
    print x

这对我来说更有用。我从未看过生成器。它们听起来很可怕(因为我在通常很难使用的模块中看到了它们)。 - ChewyChunks

2

使用 intersection 或者 intersection_update

  • intersection :

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    ans = sorted(set(a).intersection(set(xyz)))
    
  • intersection_update:

    a = [2,3,4,5,6,7,8,9,0]
    xyz = [0,12,4,6,242,7,9]
    b = set(a)
    b.intersection_update(xyz)
    

    then b is your answer


2
根据这篇文章:https://towardsdatascience.com/a-comprehensive-hands-on-guide-to-transfer-learning-with-real-world-applications-in-deep-learning-212bf3b2f27a,我使用了以下代码,同样的原因,它也很好地运行了:
an_array = [x for x in xyz if x not in a]

这行代码是程序的一部分!这意味着XYZ是一个需要事先定义和赋值的数组,以及变量a。

使用生成器表达式(推荐在所选答案中)会带来一些困难,因为结果不是一个数组。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接