ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

python:表达式

python:表达式 本章的内容, 将会对组成表达式的各个元素的意义进行解释说明。在本章以及之后的章节里面, 都会用到一种叫做扩展 BNF 的标注方式, 这种方式被用来描述语法结构, 而不是去描述词法分析的过程。当某种替代的语法规则出现了下面这样的形式的时候。name ::假如并没有对语义做出说明, 那么这类形式的name在结构规则上就属于一致的情况。6.1. 算术转换如果对下面这个算术运算符的任何说明里面, 提到了数值类型的参数被转换成了通用类型这样的一件事情, 那么就代表在底层实现里, 针对这种内置类型的运算操作, 是按照以下这种方式来进行运转和处理的。只要其中的任意一个参数具备复数这样的性质, 那么另外一个相关的参数便会被迫地转变为复数形态。否则的情况是, 如果出现任何一个参数它是属于浮点数类型的, 那么另一个参数就会被转换成浮点数类型。否则的话, 这两个量都应当是整数的形式是不需要进行任何的转换操作的。存在一些附加的规则, 这些规则会去作用到特定的运算符上面, 比如在某个例子里头, 字符串会作为百分之符号这种运算符的左边的运算参数。因此, 扩展必须要去定义属于它们自己的那种转换行为。6.2. 原子“原子”这个词儿, 指的是表达式里头那些最基本最原始的构成成分。说简单点, 标识符还有字面值那类东西, 就是最简单的原子例子。不过要是用圆括号、方括号呀或者是花括号把什么东西给包起来了, 这种形式在语法分析的时候, 照样被分到了原子的范畴里去。至于这些原子具体来说长什么样子, 它们的句法结构是这样的:atom :: | |:: | | || |6.在标记这一概念方面, 需要对它的名称进行明确的界定。作为原子出现的标识符叫做名称。请参看标识符和关键字一节了解其词法定义, 以及命名与绑定获取有关命名与绑定的文档。当名称绑定了对象的时候, 对那个原子进行求值的话, 就会返回对应的对象。在名称还没有被绑定好的情形之下, 如果尝试去对它的值进行求取操作的话, 将会引发异常错误。私有名称转换是指: 当一个标识符在类定义中被当作文本出现时, 如果这个标识符的开头有两个或者更多的下划线, 但是结尾却少于两个或者完全没有下划线, 那么它就会被当成是这个类的私有名称。这种私有名称会在为它生成代码之前发生形态上的改变, 变成一种更长格式的名字。在具体进行的转换操作里, 系统会把类的名称塞进这个名字里面去, 然后把它原本开头的下划线拿走, 最后在剩下的名字部分的最前面再加出一个下划线。比如, 在一个叫做 Ham 的类里面出现的标识符, 会被进行一些转换处理。这种转换操作是独立的, 它不会受到该标识符在什么地方使用、或者是什么样的语法结构的影响。要是转换之后的名字长度超过了 255 个字符那么多, 那么就有可能出现截断的情况, 至于具体会怎么样截断, 是由软件的实现方式自己来决定的。还有一种特殊情况, 就是如果这个类的名字全部都是下划线构成的, 那么就不会发生任何转换操作。6.2.2. 字面值该功能不仅提供对字符串这种格式的字面值的支持, 同时也支持字节串这样的字面值存在, 并且还涵盖了多种不同类型的数字字面值。:: || | |对字面值进行求值操作之后, 会返回一个与该值所属的类型相对应的对象。这个类型可能包括字符串、字节串、整数、浮点数或者复数。针对浮点数以及虚数也就是复数的情况来看, 最终得到的值有可能是近似值。如果想要了解更详细的信息, 可以参见关于字面值这部分内容。所有的字面值都会对应于那些没法改变的变量类型, 所以啊, 对象标识这事儿的重要性, 就不如它真实的那点值重要了。当你不管是在代码原处的同一个位置, 还是在不一样的位置上, 反复对着拥有同样值的字面值去求值的时候, 你有可能拿到的是那么同一个东西, 也有可能是拿到的不同玩意儿, 但这些不同玩意儿里头, 装的都是同一个实打实的价值内容。6.第2点3小节的内容是, 采用圆括号这种书写形式的。这种带有圆括号的形式, 意思就是, 那些被包括在圆括号里的内容, 是一个可选的表达式列表。:: “(”“)”带圆括号的表达式列表可以返回该列表所生成的任何东西, 如果这个列表里面包含至少一个逗号, 它就会产生一个元组, 否则的话, 它会产生由该表达式列表对应的单一表达式。一对内容为空的圆括号将会产生一个空的元组对象。因为元组是一种不能改变的对象, 所以这里适用与字面上一样的规则。也就是说, 两次出现的空元组所产生的对象, 情况可能是相同的, 也可能是不同的。大家需要注意一下, 其实元组并不是由圆括号来构建的, 真正起作用的是逗号操作符。但是呢也有个例外情况, 当我们要创建空的元组时, 这时候圆括号才是必须的。为啥要这样规定呢?主要是为了避免出现歧义, 如果允许在表达式中使用那种不带圆括号的 “空” 的东西, 就会导致意思不明确。另外, 这也能防止一些常见的输入错误发生, 并且让这些错误不会被立刻发现并捕捉到。6.在第2.4部分当中, 需要对列表、集合以及字典的具体显示效果进行展示。为了构建列表、集合或字典, 提供了名为显示的特殊句法结构, 每种类型分别存在两种具体的呈现形式。第一种方法是将容器内部的内容明确地列出来。第二种情况是借助于一组来进行循环的操作还有进行筛选的指令从而计算出了一个结果, 这个结果有一个专门的叫法就是被称为推导式。推导式这种语法结构其通常所涉及到的基本组成成分是::: n::“async”“for”就是“in”。:: |:: “if”推导式其实是由一个单独的表达式以及后面的至少一个 for 子句, 还有零个或者更多的 for 或者 if 子句共同构成的。在这样的结构下, 生成新容器中元素的方法, 就是把每一个 for 或者 if 子句都当成是独立的代码块来看待, 让它们按照从左往右的方向进行层层嵌套的逻辑处理, 然后每一次当程序执行流程推进到这个最内层的代码块位置的时候, 就去把那个表达式拿出来运算一下, 通过这样的运算结果来产生出一个新的元素放在里面。不过除了最左边 for 子句中的可迭代表达式推导式是在另一个隐式嵌套的作用域内执行的。 这能确保赋给目标列表的名称不会“泄露”到外层的作用域。位于最左侧那个 for 子句里面的可迭代对象表达式, 会直接在外面那一层作用域中被算清楚结果, 并且把这个结果当作一个参数, 递进到里面那个看不见的、嵌套起来的作用域里去。后面那些其他的 for 子句呢, 还有在最左边那个 for 子句里出现的所有用来挑选数据的筛选条件, 它们不能在外层那个可见的作用域中间就被提前算出来, 是因为这些东西很有可能得靠着从最左边那个可迭代对象里头取出来的数据值, 才能进行后续的操作。例如:对于x取十个以内的数, 然后对每一个这样的x, 让y在从x开始一直到它加十个为止的范围里跑一遍, 最后把x和y乘起来的结果列出来。为了让推导式在运行之后, 其结果始终都是一个类型完全正确的容器类型对象, 所以必须在那些隐含的嵌套作用域内部, 坚决禁止使用 yield 以及 yield from 这两个具体的表达式。从3.6版本开始, 在async def函数里, 大家能用async for这个子句来进行迭代。要想在async def函数里面构建推导式呢, 你可以在最先开头的那个表达式后面加上一个for子句或者是一个async for子句, 当然这种情况也可能会包含有额外的for子句或者是额外的async for子句的, 它甚至还可能会用到await这个表达式。如果一个推导式里包含有异步的 for 子句, 又或者包含了 await 表达式的时候, 那么这种推导式就会被叫作是异步推导式。因为它是可以暂停它自己所在的那个协程函数的执行的。另外的话, 请你也顺便去查看一下 PEP 530 的相关内容吧。3.第6个新版功能里面添加了异步推导式的东西。关于 3.8 版本的更新说明, 需要特别注意的是, 在那些没有被明确声明的、处于隐层嵌套关系的作用域内部, 已经不再支持使用 yield 关键字以及 yield from 表达式了, 也就是说这两种用法在当前这种情境下已经被禁止操作。6.对2.5小节中提到的列表显示情况进行具体的呈现。所谓的列表显示操作, 其实就是把一些东西放到方括号里边, 这些东西呢, 可以是一个空的序列, 也可以是有一些表达式的系列, 反正就是这样一个意思。:: “|列表显示这种方式会生成一个全新的列表对象, 那个对象里的内容, 是使用一连串表达式或者是一个推导式来确定的。当你给出用逗号隔开的多个表达式时, 这些元素会从左边开始到右边, 一个接一个地进行计算, 并且按照算出来的顺序, 一个个地放到这个新的列表对象里去。要是你提供一个推导式的话, 那么这个列表就会根据那个推导式所生成的结果元素, 一步步地构造出来。6.2.6. 集合显示集合这种数据的呈现方式, 是使用花括号来进行标注的, 它和字典在显示上面的不同地方在于, 集合里没有那种通过冒号来分开键和值的部分。:: “{” ( | ) “}”集合显示会造出一个新的、能够变动的集合对象。这个对象里的内容, 是借着一连串表达式或者是用一个推导式定下来的。要是给的是由逗号隔开的一堆表达式的话。那这些元素会从左边开始往右边一个个算出结果来。然后把这些结果添加入那个集合对象里面去。要是给的是一个推导式的话。那这个集合就是靠着那些推导式生出来的结果元素, 一块儿把整个集合搭建起来的。不能使用大括号加空字符串的方式去创建一个空的集合, 因为通过这种字面量的做法来生成时, 最终得到的是一个空的字典对象而非集合。6.针对2.7节中所展示的字典内容, 进行具体的显示操作。字典显示是一个由花括号括起来的内容, 这个内容有可能是一个空的键和数据的对应关系列表。:: “{”|“}”:: (“,” )*这个符号可以被定义为是用冒号来表示的, 或者是星号的。该符号被定义为一个冒号。字典显示会产出一个新的字典对象的, 这种情况。当你给出一个由逗号分隔的键和数据对的组合序列时, 它们会从左往右依次被计算, 从而用来定义字典里的各个条目。在这里, 每一个键对象都会被当作是在字典里面存放对应数据的那个键。所以这带来的结果是, 你可以在这个键和数据的对子列表里, 重复指定好几个一模一样的键。最后形成的字典里面的值, 会由你最后一次说出来的那个键来做决定。用双星号 来表示对字典展开, 它后面跟着的操作数必须是一个映射对象, 然后, 每一个键值对都会被放进一个新创建的字典里面去, 不过要注意, 排在后面的那些值会把之前已经存在的那个旧值给替换掉, 同时也还会覆盖掉之前通过字典展开所设置好的那些内容。3.第5点, 新版功能当中包含了一项可以将数据拆解到一个字典里面并且显示出来的操作, 这项功能在最初阶段是由PEP 448这一项提议所提出来的。字典推导式跟列表推导式还有集合推导式不太一样, 它得用上两个表达式, 这两个表达式中间用冒号隔开, 然后再配上标准的 for 部分和 if 部分, 当这个推导式开始运行的时候, 会得出一些作为键的元素, 和另外一些作为值的元素, 这些元素就会按照它们被产生的先后顺序, 一个一个地加到新创建的字典里去。关于键的取值类型需要遵守哪些限制, 在之前那一部分叫做标准类型层级结构的内容里, 已经全部都列出来了。从总的方面来考虑, 通常来说, 键的类型应当是某种特定的类型, 这么做的话, 就会直接排除掉所有的可变对象。对于那些拥有重复键的情况, 系统里面是不会去专门检测其间是否存在冲突问题的。具体来讲, 最后指定给这个键的数据将会被保存下来, 也就是在所有显示出来的文本中位置最靠右边的那个数据, 它会被当作是唯一并且真正有效的数据予以接收和处理。这一情况是在3.8版中进行更改的, 在3.8之前的字典推导式中, 并没有将键和值的求值顺序定义好。在之前的版本里, 值会先于键被求值。根据PEP 572的提议, 从3.8开始之后, 键会先于值被求值。6.2.8. 生成器表达式生成器表达式这种形式, 是用圆括号把它给括起来的, 它是一个紧凑的形态, 其本质上就是一种生成器标注。这里的定义显示为两个字符串, 一个是左括号, 另一个是右括号。生成器表达式会去产生一个新的生成器对象。它的句法, 是和推导式相同的。但是它们之间是有区别的。区别在于, 它是用了圆括号来把内容括起来的。而并不是用了方括号, 或者是花括号来进行包括。当对生成的那个生成器对象调用next方法的时候, 生成器表达式里用的那些变量, 就会以一种懒洋洋的方式等着被去求值, 这个情况呢就跟普通的生成器是一样的。不过呢, 这里还有个特殊情况, 那就是最左边那个for子句里面放的那个可迭代对象, 它是要立刻就被求值的, 所以说, 因为这个产生的错误, 会在你这个生成器表达式刚刚开始定义的时候就被检测出来, 而不是说要等到你去取第一个值的那个大事儿发生时才冒出错的。后面那个 for 子句, 还有最左边那个 for 子句里面所有的筛选条件, 都不能在外层的作用域里去求值。这是为什么呢? 因为这些条件可能会依赖从最左边的那个可迭代对象中获取到的具体值。比如说这个例子, 就是 (x*y for x in range(10) for y in range(x, x10))。在那些只附带一个参数的调用场合, 圆括号是可以被省略的。详细的关于这事情的说明, 参见调用这一节的内容。为了不让生成器表达式本身的预期工作受到干扰, 所以在那种由程序自动去弄出来的地方, 是不允许用那些 yield 这样的表达式, 也是不允许用那些 yield from 这样的表达式的。如果一个生成器表达式里包含了异步迭代子句, 也就是那个叫做 async for 的组成部分, 或者也包含了异步等待表达式也就是说 await 这种写法的话, 那么它就会被称作异步生成器表达式。这个异步生成器表达式在运行后会返回一个新的异步生成器对象出来。然后, 返回的这个新对象是属于异步迭代器这一范畴的。具体的细节大家可以去參考一下关于异步迭代器的相关说明内容。3.第 6 点, 新版功能有变化, 加入了异步生成器表达式。在 3.7 版更改: 在 3.7 之前异步生成器表达式只能在 async def 协和中出现。 从 3.7 开始任何函数都可以使用异步生成器表达式。在 3.8 版更改: yield 和 yield from 在隐式嵌套的作用域中已被禁用。6.在2.9节中, 我们将对yield这个表达式的含义进行探讨。该符号的含义是左边的括号和右边的空括号。:: “yield”| “from”yield 表达式只会在定义函数的时候才被用到。所以在函数的内部才能去用 yield 表达式。如果在一个函数里用了 yield 表达式, 这个函数就会变成生成器。要是你在一个用 async def 定义的函数里用了 yield 表达式, 那么这个协程函数就会变成一个异步的生成器。例子如下:def gen(): # ayield 123异步函数 agen 被定义出来了, 也就是 an。yield 123因为它们会对外面的那个作用域产生一些连带的不良影响, 所以 yield 表达式是不被允许的。这个不被允许的情况出现在哪里呢?它出现在用于实现推导式以及生成器表达式的那个隐式的定义作用域里面。在 3.8 版更改: 禁止在实现推导式和生成器表达式的隐式嵌套作用域中使用 yield 表达式。这里对生成器函数做出了相关介绍。如果读者需要了解异步生成器函数, 需要前往异步生成器函数这一章节查看独立的详细说明内容。当一个生成器函数被调用的时候, 这个行为会返回一个迭代器, 这个迭代器的名字就是生成器。之后, 由这个生成器来掌控生成器函数的执行过程。只要这个生成器里面的某一个方法被调用了, 那么生成器函数就开始了执行动作。接下来, 程序就会一直执行下去, 直至遇见第一个 yield 表达式的地方。在这个时候, 当前的执行流程会被再次挂起, 并且, 把某个特定的值返回给生成器的调用者。在挂起这个阶段之后, 我们就来说一说, 所有局部状态其实都是被完整保留下来的。这种保留包含了局部变量此刻的绑定情况, 还有指令指针的位置, 也包括内部的求值栈的情况, 以及其他任何与异常处理相关的状态。接下来, 如果我们去调用生成器对象当中的某一个方法那么生成器函数就会继续往后执行。这时候, 函数的运行状况会和这样一种情境完全一致, 即把 yield 表达式仅仅看作是外部函数的一个常规调用来处理的。等到恢复执行之后, yield 表达式最终所产生的那个值, 到底是多少, 这要取决于你具体是用来哪个方法把这个过程给恢复起来的。要是使用的是 next 这个函数的话, 通常情况下大家会通过语言内置的 for 循环功能, 或者直接调用 next 方法去执行, 那么所得到的最终结果就是 None。除此之外呢, 假若选择的是 send 这个方法, 那得出的结果会是你直接传递给 send 方法的具体的那个值。所有这些情况让生成器函数与协程看起来非常类似。它们会反复使用yield语句。它们拥有不止一个入口点。它们的执行过程是可以被暂停的。这里存在唯一的一个差别, 那就是生成器函数自己无法掌控在通过yield交出权力之后应该去哪里恢复执行。控制权始终都会转交给生成器的调用者。在 try 代码块里, 任何一个地方都能使用 yield 表达式。要是生成器还没恢复执行, 就因为引用计数变零, 或者是被垃圾回收机制给清理掉了, 那么系统就会去调用那个生成器对应的迭代器的 close() 方法。这个 close 方法的存在, 是让那些还挂在那儿没执行的子句有机会去执行完。在使用 yield from 这个写法的时候, 那个被提供的表达式, 它必须得是一个能被迭代的东西, 也就是我们常说的可迭代对象, 当我们去处理这个可迭代对象时, 它所生成出来的那些值, 会直接把结果发送给正在调用当前这个生成器方法的人, 也就是调用者。如果不是面临这样的实际情况, 那么函数send()可能会引发某种错误或者导致某种状况发生, 与此同时, 函数throw()会立刻抛出不论什么类型的传入异常。现在下层迭代器执行结束了, 然后那个被抛出来的实例里的 value 属性, 就会变成 yield 表达式的最终结果。这个值你可以在抛出的时候手动去设定的, 还有一种情况就是当子迭代器本身是个生成器的时候它会自己搞定, 也就是通过让这个子生成器最后返回一个值来实现的。在版本 3.3 进行了修改, 添加了 yield from 这一功能, 用于将控制流委托给一个子迭代器。如果说那个用yield这个词来表示的表达式, 它是放在等号右边作为赋值操作唯一的那个内容的情况下, 那么这周围的括号是可以选择去掉不写的。参见PEP 255 - 简单生成器在相关提议中, 引入了生成器功能以及用来暂停和恢复函数执行的关键字 yield。PEP 342 - 通过增强型生成器实现协程有人提出了增强生成器 API 和语法的方案, 这样一来, 它就完全可以被拿来当作简单的协程使用。PEP 380 - 委托给子生成器的语法引入语法, 目的是为了方便地将权力委托给子生成器, 这项提议正在被提出。关于PEP52号文件, 这一异步生成器的相关内容。这个提议的内容是, 为了实现对 PEP 492 规定的扩展操作, 需要在已有的协程函数代码结构内, 进一步添加入生成器所具备的特定功能特性支撑。6.对于2.9.1这个章节标题, 也就是生成器与迭代器相关的方法这部分内容。这个子小节专门讲了生成器迭代器的方法是什么, 这些东西能够拿过来用来控制生成器函数是怎么运行的。请您务必要留意, 一旦生成器处于正在运行的状态, 此时若去调用下方的那些个方法, 就会触发异常的这种状况。.next()你要去启动一个生成器函数, 并且开始执行它。或者, 你是要接着上次没做完的地方继续干下去。这个上次没做完地方, 就是那个表达式的产出值所在的位子。当你调用那个 next 方法让生成器恢复运行的时候。此刻的那个产出表达式它肯定是没有值的, 也就是说它的值是空的。然后程序会一直跑啊跑, 跑到下一个产出表达式那里为止。接下来这个下面一个产出表达式的结果, 就会被返回给你刚才那个调用 next 的人手中。如果在生成器对象试图产生下一个值的时候, 它选择了退出操作, 那么系统就会触发抛出相应的异常错误情况信息提示。这种做法常常是被暗中调用的, 比如借着 for 这种循环的方式, 或者是借助于 next() 这个内置功能来完成调用。
返回列表