python列表生成式原理,python列表生成器语法
Yyds干货库存
如果我必须选择Python最喜欢的特性之一,毫无疑问,那就是列表解析。在我看来,它们概括了“Pythonic式”代码的本质.这很讽刺,因为它们实际上是从Haskell借来的。
找到他们我很兴奋。这是我几周前计划写的一篇文章,但是我意识到理解迭代器对于真正理解列表解析及其潜力是必不可少的。
如果你还没有看过前两篇关于《循环和迭代器》和《迭代工具》的文章,那么你现在可以回去看看。
解析和生成器表达式生成器表达式是一种生成容器的简洁方法。最常见的是,您会听到列表解析,但也有集合解析和字典解析。然而,术语上的差异有些重要:如果您实际上正在创建一个列表,那么它只是一个列表解析。
生成器用括号()括起来,而列表解析用括号[]括起来。集合用大括号{}括起来。除了这些差异,所有三种情况的语法都是相同的!
(还有更多关于字典分析的内容,我们将在后面讨论。)
生成器与生成器相关,我们会在后面的部分深入讨论。现在,我们只需使用生成器表达式的官方定义就足够了:
生成器表达式-返回迭代器的表达式。
生成器的结构生成器表达式(或室外组列表/集合)有点像翻转的for循环。
举个简单的例子,让我们回顾一下上一篇文章中的一个例子,我们将华氏温度列表转换为摄氏温度。我会稍微调整一下,所以数字会存储在另一个列表中,而不是直接打印出来。
temps_f=[67.0,72.5,71.3,78.4,62.1,80.6]
temps_c=[]
定义温度:
返回回合((温度- 32)/1.8,1)
对于map中的c(f _ to _ c,temps_f):
临时_c .追加(c)
Print(temps_c)信不信由你,列表解析会把整个程序减少到三行!让我们一次减少到一部分,以便你能理解我的意思。
让我们从用列表解析替换for循环开始。
temps_f=[67.0,72.5,71.3,78.4,62.1,80.6]
定义温度:
返回回合((温度- 32)/1.8,1)
temps _ c=[f _ to _ c(temp)for temps in temps _ f]
Print(temps_c)重要的一行代码是temps _ c=[f _ to _ c(temp)for temps in temps _ f]。这与map()的行为非常相似。对于temp列表中的每个元素temps_f,我们应用函数f_to_c()。
现在,如果我在别的地方需要f_to_c()函数,我就在这里停下来结束。但是,如果这是我唯一需要华氏-摄氏转换逻辑的地方,我就根本不能使用这个函数,直接将逻辑代码移到解析中:
temps_f=[67.0,72.5,71.3,78.4,62.1,80.6]
temps_c=[round((temp-32)/1.8,1)for temps in temps _ f]
我告诉过你什么?三条线!难道只有三行代码!
根据我得到的数据,我甚至可以进一步降低。让我们看另一个例子。
假设您有一个程序,它在一行上接收一串整数,用空格分隔,比如5 4 1 9 5 7 5。你想找出所有这些整数的和。(为了简单起见,假设你没有冒输入错误的风险。)
我们先用显而易见的方式写,不用列表解析。
用户输入=输入()
values=user_input.split( )
总计=0
对于v in值:
n=int(v)
总计=n
印(总)显而易见吧?我们以字符串的形式获取用户输入,然后用空格分割列表以获得每个数字。我们创建一个变量来存储总数,然后使用循环遍历每个值,将其转换为整数,然后将其添加到总数中。现在我们有了代码逻辑,让我们简化和优化它。
这里先简化一些显而易见的代码。我们之前已经介绍了所有这些概念,所以看看你是否能找到我可以改进的地方。
值=输入()。拆分(“”)
总计=0
对于v in值:
total=int(v)
Print(total)除非我们使用列表解析,否则不可能比这更简单,所以现在就开始吧!
值=输入()。拆分(“”)
total=sum(int(v) for v in values)
Print(total)这里的生成器表达式是(int(v) for v in values)。v对应于列表中的每个值,我们将其转换为整数(int(v))。
请注意我是如何使用这个sum()函数将生成器表达式直接传递给它的。由于表达式是直接作为唯一的参数传递的,所以我不需要给它加上额外的括号。
现在,如果我不需要任何其他的值列表,我可以直接将该逻辑移到生成器表达式中!
total=sum(int(v) for v in input()。拆分(“”)
打印(总)就像做馅饼一样简单吧?
嵌套列表解析如果反过来,我们要输入的每个数字的平方和呢?事实上,有两种方法可以做到这一点。简单的方法是这样的:
total=sum(int(v)* * int(v)for v in input()。拆分(“”)
打印(总)这个管用,但不知何故就是感觉不对劲,不是吗?我们要把V转换成整数两次。
我们可以通过在生成器表达式中嵌套列表解析来解决这个问题!
total=sum(n * * 2 for n in[int(v)for v in input()。split(“”)])列表解析和生成器表达式是由内而外解析的。input()中v的最内层表达式int(v)。split(),先运行,闭方括号[]将其转换为列表(迭代)。
接下来[LIST]中n的n**2运行外部表达式,[LIST]就是我们刚刚生成的列表。
这种嵌套不容易理解和直观。尽量少用。当我需要嵌套时,我将每个列表理解写在单独的行上并存储它。
the_list=[int(v) for v in input()。拆分(“”)]
total=sum(列表中n的n**2)
打印(总计).测试一下,然后通过复制粘贴开始嵌套。
表达式中的条件如果我们只想要列表中奇数的和呢?生成器和列表解析也可以做到这一点。
在这个例子中,我们将使用嵌套,但是我们将首先从非嵌套版本开始,以便使新的逻辑更容易查看。
the_list=[int(v) for v in input()。拆分(“”)]
total=sum(如果n%2==0,则n * 2表示_list中的n)
打印的新部分(总)在第二行。在生成器表达式的最后,我添加了if n%2==0。你可能知道模运算符(%),它为我们提供了除法的余数。任何偶数都可以被2整除,这意味着它没有余数。所以n%2==0只适用于偶数。
把条件放在语句之后而不是之前,感觉有点奇怪。理解它最简单的方法就是和没有生成器表达式的相同代码逻辑进行比较。
输出=[]
对于列表中的n:
如果n%2==0:
Output.append(n**2)基本上,要将其转换成生成器表达式,只需要知道append()并从这里开始。
n**2
对于列表中的n:
如果% 2==0:那么从for和If语句中删除冒号(:)和换行符。
如果n%2==0,则列表中的n为n**2
迭代多个对象。我们还可以使用生成器表达式和列表解析在一个循环中迭代多个可迭代对象,就像嵌套循环一样。
考虑以下逻辑:
数字a=[1,2,3,4,5]
数字b=[6,7,8,9,10]
输出=[]
对于num_a中的a:
对于num_b中的b:
Output.append(a*b)我们也可以按照我刚才给出的相同步骤将其转换为列表解析!我们把append()的参数放在前面。
a*b
对于num_a中的a:
在编号b中:然后我们把剩下的折叠成一行,删除冒号。
* a*b for a in num_a for b in num_b _ b最后用方括号括起来,复制到变量output output。
output=[a * b for a in num _ a for b in num _ b]
解析集合正如我在文章开头提到的,就像你可以用方括号[]括起来的生成器表达式创建一个列表一样,你也可以用花括号{}创建一个集合。
例如,让我们生成一组所有的余数,这些余数是100除以小于100的奇数得到的。通过使用集合,我们确保没有重复,使结果更容易理解。
odd _ reminders={ 100% n,n在(1,100,2)范围内}
Print (odd _ remaining)给我们运行这段代码.
{0,1,2,3,5,6,7,8,9,10,11,13,14,15,16,17,18,19,21,22,23,25,26,27,29,30,31集合解析的工作方式与列表解析相同,只是创建了不同的容器。
字典解析字典解析遵循与其他形式的生成器表达式几乎相同的结构,但有一点不同:冒号。
如果你记得,当你创建一个集合或字典时,你使用花括号{}。唯一的区别是,在字典中,您使用冒号:来分隔键值对,这是一个不会在集合中执行的操作。同样的原则也适用于此。
例如,如果我们要创建一个字典,我们将存储1到100之间的整数作为键,并将该数字的平方作为值。
squares={n : n**2 for n in range(1,101)}
打印(方块)这是字典分析!除了冒号:其他的都和其他生成器表达式一样。
在所有有风险的情况下,使用列表解析或生成器表达式可能很有诱惑力。它们相当容易让人上瘾,部分原因是它们写出来的时候看起来非常高端和优雅。强大的单行代码让程序员非常兴奋。我们真的喜欢优雅地处理我们的代码。
但是,我必须提醒你,不要过于追求优雅。还记得Python的Zend吗,下面是一些和这个话题相关的部分:
美比丑好。
.
简单比复杂好。
复杂比复杂好。
平面比嵌套好。
疏比密好。
可读性非常重要。
.
解析列表可能很优雅,但是如果使用不当,它们也可能变成密集的垃圾逻辑。
1.代码变得难以阅读。这个例子是我从网上的一个调查中借用的。
PRIMARY=[c for m in status[ members ]if m[ stateStr ]= PRIMARY for c in RS _ config[ members ]if m[ name ]==c[ host ]]
SECONDARY=[c for m in status[ members ]if m[ stateStr ]= SECONDARY for c in RS _ config[ members ]if m[ name ]==c[ host ]]
hidden=[m for m in RS _ config[ members ]if m[ hidden ]]能说出这段代码的意思吗?读一段时间,也许可以,但是为什么要读呢?代码很清楚。(在调查中,这被评为可读性最差的例子。)当然,您可以添加注释来解释正在执行的逻辑——事实上,在最初的示例中他们就是这样做的——但是任何时候您需要注释来解释代码在做什么,这几乎肯定是太复杂了。
解析和生成器表达式功能强大,但是如果大量使用,就会变得像刚才一样难以阅读。
不一定要那样。您可以通过将列表理解分成多行来重新获得大量可读性,其结构类似于传统的循环。
主要=[
c
对于状态为[成员]的m
if m[ stateStr ]= PRIMARY
对于rs_config中的c[ members ]
if m[名称]==c[主机]
]
次要=[
c
对于状态为[成员]的m
if m[ stateStr ]= SECONDARY
对于rs_config中的c[ members ]
if m[名称]==c[主机]
]
隐藏=[
m
对于rs_config中的m[ members ]
如果m[隐藏]
请注意,这并不能完全证明上述说法是正确的。我仍然会使用传统的循环来代替前面的例子,只是因为它们更容易阅读和维护。
如果你还不相信这是Python的一个容易被滥用的特性?我的一个朋友分享了他遇到的这个真实案例。我们不知道它做了什么。
cro pids=[self . roidb[inds[I]][ chip _ order ][
self . crop _ idx[inds[I]]% len(self . roidb[inds[I]][ chip _ order ])]for I in
Range(cur_from,cur_to)]光看着,我的可能会有点烦躁。
2.他们不会取代循环。看下面。(此代码为虚构,仅供参考。)
some _ list=getthedatafromworever()
[api.download()。process (foo) for foo in some_list]对于没有经过训练的人来说,这个似乎没什么问题,但是请注意,请仔细观看.数据some _ list正在被直接修改(变异),但结果没有被存储。这就是列表解析甚至生成器表达式被滥用而不是循环的情况。它使阅读变得困难,更不用说调试了。
无论您是否希望使用生成器表达式更优雅,在这种情况下您都应该坚持使用循环:
some _ list=getthedatafromworever()
对于some_list中的foo:
API.download()。进程(foo)
3.它们很难调试。想想列表解析的本质:你把所有东西打包成一个庞大的语句。这样做的好处是你省去了一堆中间步骤。不利的一面是……你省去了一堆中间步骤。
考虑调试一个典型的循环。您可以单步执行它,遍历它,并使用调试器随时观察每个变量的状态。您还可以使用错误处理来处理异常的边缘情况。
相反,这些不适用于生成器表达式或列表解析。一切正常或者不正常!您可以尝试解析错误和输出,以找出您做错了什么,但我可以向您保证,这是一个令人困惑的体验。
您可以通过在第一个代码版本中避免使用列表解析来避免这种疯狂!使用传统的循环和迭代器工具以显而易见的方式编写逻辑。一旦您知道它可以工作,那么也只有到那时,您才应该将逻辑折叠到生成器表达式中,并且只有在您可以在不避免错误处理的情况下这样做的时候。
这听起来像是很多额外的工作,但是我在平时的代码编写中遵循了这个模式。我对生成器表达式的理解通常是我相对于没有经验的竞争对手的主要优势,但我总是先写标准循环:我不能浪费时间调试生成器表达式中的错误逻辑。
摘要解析、生成器表达式、集合解析和字典解析是Python令人兴奋的特性。它们允许你编写非常强大和优雅的代码。
然而,它们并非没有局限性和缺点。在使用生成器表达式之前,您应该仔细权衡。如果您决定使用一个,最安全的做法是首先使用标准循环和迭代器编写逻辑,然后将其重写为生成器表达式。
让我们回顾一下要点.
该表达式遵循iterable if条件中名称的结构表达式。(可选)此if部分可以省略。多重for.in和if部分可以在一个生成器表达式中使用。通过将最里面的代码移到前面并删除其余语句后面的冒号,通常将它们都移到一行,可以将标准循环和条件块更改为生成器表达式。允许嵌套生成器表达式和列表解析。解析列表产生一个列表。它是一个包含在方括号中的生成器表达式[]。集合解析产生一个集合。它是一个用花括号括起来的生成器表达式{}。字典解析产生一个字典。它是一个用花括号括起来的生成器表达式,表达式中{}的键值对用冒号分隔:任何形式的生成器表达式都不能完全取代标准循环。应用它们时要小心,尤其是当它们难以阅读、理解或调试时。表达式只是生成器的冰山一角。我们将在下一节探讨这个主题。
原创作品来自程,
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。