Python学习笔记02–List推导式zhoukeli2005

Python语言以简易明了著称,但初次学习Python,却被很多语法搞的昏头涨脑。Listcomprehension绝对是其中之一。

一、困惑

问题一:列表推导式中的变量,是全局变量?还是局部变量?还是闭包变量?

注:一个简单的列表推导式,如下:

a=[xforxinrange(10)]

这里的变量x,是局部变量吗?在列表推导式结束后,还能访问变量x吗?

问题二:列表推导式,推导过程到底是从左往右?还是从右往左?

a=[(x,y)forxinrange(2)foryinrange(3)]

如果写成伪码,是:

forxinrange(10):foryinrange(10:a.append((x,y))

还是:

foryinrange(10):forxinrange(10):a.append((x,y))

虽然这个问题看起来很奇葩,但让我很困惑。

问题三:列表推导式中,for语句和if语句之间的关系是什么呢?

注:不光是for语句和if语句之间的关系,多个for语句之间,多个if语句之间,for语句和if语句之间,它们的关系又是什么样的呢?

问题四:列表推导式,到底是怎么运行的呢?

注:虽然看到列表推导式,凭借猜测,也能猜个八九不离十。但总不知道List推导式具体如何运行的,心中总不踏实。

二、释惑

关于Listcomprehension,有着千言万语的困惑:当里面夹杂着多个for、if语句时,如何读懂?更要命的是:如何写Listcomprehension才是正确的呢?

关于这种种疑问,归根到底,是不了解Python是如何处理Listcomprehension的。

1.关于GET_ITER、FOR_ITER字节码(opcode)

Python中有迭代的概念,最常用的要数for循环了。一个简单的for循环如下:

1:forxinA:2:do_something3:

对于这个for循环,Python会大致生成如下字节码:

1:iter=GET_ITER(A)2:x=FOR_ITER(iter)3:ifnotx:jumpto64:do_something…5:jumpto26:(TheEnd)

在这里面,起到重要作用的,就是GET_ITER、FOR_ITER这2个字节码。

其中,GET_ITER是取出一个Object的迭代器(调用PyObject_GetIter(),如果是一个类的对象,调用其__iter__方法);

之后,就会不断对这个迭代器执行FOR_ITER指令(如果是一个类的对象,调用其next方法);

如果FOR_ITER指令迭代不到下一项了(通常是遇到StopIteration异常了),就跳出for循环,否则会一直迭代下去。

2.关于POP_JUMP_IF_FALSE指令

这个指令比较长,第一次见有些被吓到,不知是干什么的。

但是仔细一看,这个指令还是比较好理解的:

首先,Python的每个函数都有自己的运行栈,所有的临时运算结果都要放在这个栈上的,例如以下简单代码:

1:ifa>0:2:do_something

这里的变量a,如果是个全局变量,那么它存在全局变量Dictionary里;如果它是个局部变量,那么它存放在局部变量数组里;如果它是个闭包变量,那么它存放在闭包变量数组里,总之,它是不在函数运行栈里的。

但是,变量a和常量0的逻辑比较的结果,是一个临时的运算结果,这个运算结果是要放在函数运行栈里的,以方便后面if判断时使用。

那么,Python对这么个简单的if代码,会大致生成以下字节码:

1:进行a>0判断,2:Push(结果)3:POP_JUMP_IF_FALSE4:do_something…5:(Theend)

Python会在逻辑运算后,将逻辑运算的结果自动Push进函数的运行栈内。那么,在执行指令POP_JUMP_IF_FALSE时,会进行下面的操作:

//POP_JUMP_IF_FALSE1、x=POP()2、ifnotx:jump

POP_JUMP_IF_FALSE指令,其实就是将栈顶的元素(一般是刚进行逻辑运算的结果)Pop出来,然后判断其是否为False,如果是False,那么就跳转,否则什么事也不做。

3.Listcomprehension的语法

在刚看到ListComprehension时,很不能理解这个语法,总会有一个疑问:在ListComprehesion中,是否只能写for和if语句?能否写while语句?能否写try-except-finally语句?而且for语句和if语句之间的关系是什么?

有很多疑问,最终还得看Grammar/Grammer这个文件中,定义的语法规则。

其中,Listcomprehension的规则,在Grammar文件中,称为listmaker。

listmaker分为2种,最简单的一种,如下:

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

也就是直接列出List中的所有元素。这种方式最简单,也最好理解。

第二种就是本文所说的ListComprehension了,语法如下:

1、listmaker:testlist_for2、list_for:for’explist‘in’testlist_safe[list_iter]3、list_iter:list_for|list_if4、list_if:if’old_test[list_iter]

语法文件全是正则表达式,而且前后相互引用,读起来非常吃力。不过在上面所列的这4行语法规则中,可以看到:listcomprehension中,只能使用for和if这2种语句。这也解决了一大部分疑问。

而且可以从上面的语法中看出,每个for语句后面,还可以接一个for语句或者一个if语句;每个if语句后面,也可以接一个for语句或者一个if语句;并且没有对for语句、if语句的个数有任何限制。

如果注意看上面关于list_for语法的规则,可以发现里面有一个叫testlist_safe的东西,这里要和Dictionary的推导语法规则对照一下,会发现很有趣的现象。

Dictionary推导式的一部分语法规则如下:

comp_for:'for'exprlist'in'or_test[comp_for]这里的comp_for语法规则,几乎和上面的list_for语法规则相同,唯一不同的是在list_for语法规则中的testlist_safe位置上,变成了or_test。

只从字面看来,testlist_safe和or_test相比,中间有一个’list’单词,也就是说:testlist_safe可以是一个列表,而or_test不可以,举例如下:

a=[xforxin1,2,3,4]

在构造列表a时,可以直接在for…in…中,列出所有的元素,即上面的“1,2,3,4”;

但是,如果是在构造一个Dictionary(或Set),如下:

a={xforxin1,2,3,4}语法解析是会报错的!因为在Dictionary(或Set)的推导式语法规则中,for…in…中,不能是所有元素的列表!

为什么会有这种不同的语法呢?我还没搞明白!

另外,还会发现testlist_old,里面还一个”old”单词,这个很容易引起头疼,因为加了”old”这个单词,很有可能是为了和老版本兼容而出现的语法规则。而历史遗留问题,是最让人头疼的问题了。

在Python的语法规则里面,还有一个叫做testlist的语法规则,那么这个testlist_old中的”old”到底是什么意思呢?

经过几番考究,原来如下,一个简单的例子:

a=5ifb>3else2

上面是Python中类似C的三元运算符”:”的语法,在Python内部,称为”if-expr”。

这个语法,就是没有”old”的语法。而在testlist_old语法中,这种带有if-else的语法是不允许的。

为什么呢?

例如,在listcomprehension中,可以写成这样:

a=[xforxin5ifb>3else2,3]

在这个例子中,if-else本意上是:5ifb>3else2,组成一个上面所说的”if-expr”语句的,但是却和ListComprehension中的for、if语法发生了冲突:这里这个if到底是ListComprehension中的呢?还是if-expr中的呢?

为了杜绝这种歧义,Python在语法规则中,就使用testlist_old而不是testlist,使得if-expr语句不能出现在for…in…的元素列表中。

4.ListComprehension中,for语句和if语句是什么关系呢?

Python的ListComprehension中,可以使用无限多个for、if语句,该怎么去理解这些for、if语句呢?它们之间的关系是什么呢?

Python的语法解析、字节码生成,大约分为3个阶段:

1、将.py源代码,解析成语法树

2、将语法树,解析成AST树

3、根据AST树,生成字节码

对于ListComprehension,可以在第2阶段,即Python/Ast.c这个源文件中,发现for语句和if语句之间的关系。

Python在从语法树生成AST的过程中,会将ListComprehension中的for分离开来,并将每个if语句,全部归属于离他最近的左边的for语句,例如:

a=[(x,y)forxinrange(10)ifx%2ifx>3foryinrange(10)ify>7ify!=8]上面这段代码中,有2个for和4个if,分别如下:

1、forxinrange(10)

2、foryinrange(10)

3、ifx%2

4、ifx>3

5、ify>7

6、ify!=8

在AST的过程中,Python会按照for语句将上面的语句拆成2部分,分别如下:

每个if语句,从属于离他最近的左边的for语句。

下面看语法解析的第三阶段,即:通过AST生成字节码,在源代码Python/Compiler.c文件中。

在Python/Compiler.c源文件中,处理ListComprehension的代码,主要是2592行的compiler_listcomp_generator(…)函数。

这个函数,首先会生成字节码:BUILD_LIST,即生成一个新的List;然后通过自身的递归,从左到右,依次处理AST过程生成的for语句及其从属的if语句。

其中对于每一个for语句,大抵生成以下字节码:

1:GET_ITER2:FOR_ITER

然后从左到右,依次处理从属与这个for语句的if语句,大抵生成以下字节码:

1:进行逻辑判断并将结果Push进函数栈2:POP_JUMP_IF_FALSE(如果结果为False,则跳转到XX处)XX:跳转到for语句的FOR_ITER处执行,相当于continue语句

也就是说,进入每一个for语句后,先取出Object的迭代器(通过GET_ITER),然后不断对其执行FOR_ITER指令,每次迭代出一个元素,都要对从属的if语句进行判断,如果有一个为False,相当于continue,返回FOR_ITER处,迭代下一个元素。

如果所有的if都判断为True,才进入后续的for语句中执行(后续的for语句都嵌套在之前的for语句中),直到最后一个for语句执行结束(从属的if都判断为True),这时才向list中append一个新的元素。

整个过程的伪码可以如下:

forxxinxxx:ifnotxxxx:continue;ifnotxxxxx:continue;........forxxinxxx:ifnotxxxx:continue;........//到了最后一个forforxxinxxx:.......List.append(value)至此,ListComprehension的内部运行过程就搞明白了。

5.最后一个问题,列表推导式中的变量,是局部变量吗?

例如,一个简单的例子:

a=[xforxinrange(10)]这里面的变量x,是局部变量吗?在列表推导式结束后,还可以访问变量x吗?

Python的变量作用域,是在源代码Python/Symtable.c中实现的。

这里的结果是:如果变量x没有被使用过,那么变量x会成为一个局部变量,当列表推导式结束后,还可以访问变量x;否则,变量x原来的作用域是什么,现在还是什么。

(完)

上一篇:没有了

下一篇:没有了

比较欣赏这种提出问题,分析问题的模式,逻辑清晰.博主先从常见的疑问作为出发点,进而从Python的内部处理分析列表推导式的工作原理.如果能够熟练使用列表推导式,则可以节省大量的for/if代码.

THE END
1.python通过来体现语句之间的逻辑关系这样可以保证程序的鲁棒性,在遇到异常时进行相应的处理,从而保证程序的正常运行。 总结起来,Python通过灵活的条件语句、循环语句的运用,顺序结构的执行,函数的调用与返回,以及异常处理机制等手段来体现语句之间的逻辑关系。这使得Python成为了一款非常适合处理复杂问题的编程语言。https://www.python51.com/jc/123187.html
2.考研英语语句关系分类及区分方法考研英语语句之间关系主要是考查前后句子之间的逻辑关系。 句子与句子之间的关系可能是显性的,也可能是隐性的。显性的语句之间关系有明显的标志词出现,这会给考生理解文章的发展脉络带来很大方便。句际关系主要有以下几种: 一、顺接关系 后句是前句的延续或补充,标识词主要有then,after that,furthermore,also,when(https://yz.chsi.com.cn/kyzx/en/200907/20090706/27495222.html
3.科学网—MySql外键一对一,一对多,多对多表关系外键虽然能够帮你强制建立表关系 但是也会给表之间增加数据相关的约束;改也不好改,删也不好删。 删除的时候可以先删除把绑定关系表的数据,然后再删除被绑定关系表的数据。 现在绑定关系表是emp,被绑定关系表是dep;删除语句为: delete from emp where id = 4; https://wap.sciencenet.cn/blog-3445347-1282998.html
4.Python程序语法元素分析缩进可以嵌套使用什么意思缩进指每一行代码开始前的空白区域,用来表示代码之间的包含和层次关系 代码编写中,缩进可以用Tab键实现,也可以用多个空格**(一般是四个空格)**实现,但两者不能混用。建议使用四个空格的编写方式 除了单层缩进,一个程序的缩进还可以“嵌套”从而形成多层缩进,python语言对语句之间的层次关系没有限制,可以“无限制”嵌https://blog.csdn.net/qq_55016379/article/details/114675213
5.Java错题合集(3)48、一般情况下,选项是关系数据模型与对象模型之间匹配关系? 表对应类 记录对应对象 表的字段对应类的属性 49、J2EE中常用的名词解释正确的是? EJB容器:Enterprise java bean 容器 JMS:JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。 https://www.jianshu.com/p/29ec5bb08b71
6.胡敏教授解密考研阅读命题与对策者深层思路上的逻辑关系。从句子与句子,段落与段落之间的过渡可以读出作 者整个的思路及论证过程,才能做出正确的推理、判断或引申。平时训练时, 考生应多注意一些过渡词。 3.积极扩展词汇量,注意动词和名词,训练理解一些难句、复杂句。扩展词汇 量的目的是避免词汇的欠缺造成理解的失误。动词作为语句结构的枢纽,意义http://www.yuloo.com/kaoyan/news/2006-09-30/43221.shtml
7.华为HCIP华为数通工程师刷题日记1116(一个字惨)【答案解析】策略路由(PBR)的操作对象是数据包,所以if-match后面是可以跟ACL,但是不可以跟ip-prefix;机构组成类似于 route-policy:包含多个节点/条目,彼此之间是或的关系;每个节点/条目中,可以有1个条件(if-math),也可以多个条件(if-math),彼此之间与的关系;每个节点/条目中,可以有apply语句,就是修改数据包;如https://developer.aliyun.com/article/1436981
8.埋头刷了大半年Java面试题:如愿拿到众多大厂offer!分享还愿在工作中,SQL语句的优化和注意的事项 哪些库或者框架用到NIO Spring 都有哪几种注入方式,什么情况下用哪种,ioc实现原理 如何定位一个慢查询,一个服务有多条SQL你怎么快速定位 聚集索引和非聚集索引知道吗?什么情况用聚集索引什么情况用非聚集索引 Nosql引擎用的什么存储结构,关系型数据库和NoSQL各自的优劣点是什么https://maimai.cn/article/detail?fid=1735736133&efid=KbtUeCVp4epTWWihu6i76A