Python原型代表编程语言中最紧密绑定的操作。 原型代表编程语言中最紧密绑定的操作通常指的是原型继承(Prototype-based inheritance),这种继承方式与经典的类继承(Class-based inheritance)有所不同。
它们的句法如下:
primary ::= atom | attributeref | subscription | slicing | call
一、属性引用
属性引用是后面带有一个句点加一个名称的原型:
attributeref ::= primary "." identifier
此原型必须求值为一个支持属性引用的类型的对象,多数对象都支持属性引用, 随后该对象会被要求产生以指定标识符为名称的属性, 这个产生过程可通过重载 __getattr__() 方法来自定义。 如果这个属性不可用,则将引发 AttributeError 异常,否则的话,所产生对象的类型和值会根据该对象来确定。 对同一属性引用的多次求值可能产生不同的对象。
二、抽取
对一个容器类的实例执行抽取操作通常将会从该容器中选取一个元素; 而对一个泛型类执行抽取操作通常将会返回一个 GenericAlias 对象。
subscription ::= primary "[" expression_list "]"
当一个对象被抽取时,解释器将对原型和表达式列表进行求值。
原型必须可被求值为一个支持抽取操作的对象。 一个对象可通过同时定义 __getitem__() 和 __class_getitem__() 或其中之一来支持抽取操作; 当原型被抽取时,表达式列表的求值结果将被传给以上方法中的一个。
如果表达式列表包含至少一个逗号,它将被求值为包含该表达式列表中所有条目的 tuple。 在其他情况下,表达式列表将被求值为列表中唯一成员的值。对于内置对象,有两种类型的对象支持通过 __getitem__() 执行抽取操作:
1、映射:如果原型是一个 mapping,则表达式列表必须求值为一个以该映射的某个键为值的对象,而抽取操作会在映射中选取该键所对应的值。 内置映射类的一个例子是 dict 类。
2、序列:如果原型是一个 sequence,则表达式列表必须求值为一个 int 或一个 slice (如下面的小节所讨论的)。 内置序列类的例子包括 str, list 和 tuple 等类。
正式语法规则并未设置针对序列中负索引号的特殊保留条款,不过,内置序列都提供了通过给索引号加上序列长度来解读负索引号的 __getitem__() 方法,因此举例来说,x[-1] 将选取 x 的最后一项, 结果值必须为一个小于序列中条目数的非负整数,抽取操作会选取索引号为该值的条目(从零开始计数)。 由于对负索引号和切片的支持是在 __getitem__() 方法中实现的,因而重载此方法的子类将需要显式地添加这种支持。
字符串是一种特殊的序列,其中的项是字符。 字符并不是一种单独的数据类型而是长度恰好为一个字符的字符串。
三、切片
切片就是在序列对象(字符串、元组或列表)中选择某个范围内的项, 切片可被用作表达式以及赋值或 del 语句的目标。 切片的句法如下:
slicing ::= primary "[" slice_list "]" slice_list ::= slice_item ("," slice_item)* [","] slice_item ::= expression | proper_slice proper_slice ::= [lower_bound] ":" [upper_bound] [ ":" [stride] ] lower_bound ::= expression upper_bound ::= expression stride ::= expression
此处的正式句法中存在一点歧义:任何形似表达式列表的东西同样也会形似切片列表,因此任何抽取操作也可以被解析为切片。 为了不使句法更加复杂,于是通过定义将此情况解析为抽取优先于解析为切片来消除这种歧义(切片列表未包含正确的切片就属于此情况)。
切片的语义如下所述。 原型通过一个根据所下所示的切片列表来构造的键进行索引(与普通的抽取一样使用 __getitem__() 方法)。 如果切片列表包含至少一个逗号,则键将是一个包含切片项转换形式的元组;否则的话,键将是单个切片项的转换形式。 切片项如为一个表达式,则其转换形式就是该表达式。 一个正确的切片的转换形式就是一个切片对象,该对象的 start, stop 和 step 属性将分别为表达式所给出的下界、上界和步长值,省略的表达式将用 None 来替换。
四、调用
所谓调用就是附带可能为空的一系列参数来执行一个可调用对象 (例如 function):
call ::= primary "(" [argument_list [","] | comprehension] ")" argument_list ::= positional_arguments ["," starred_and_keywords] ["," keywords_arguments] | starred_and_keywords ["," keywords_arguments] | keywords_arguments positional_arguments ::= positional_item ("," positional_item)* positional_item ::= assignment_expression | "*" expression starred_and_keywords ::= ("*" expression | keyword_item) ("," "*" expression | "," keyword_item)* keywords_arguments ::= (keyword_item | "**" expression) ("," keyword_item | "," "**" expression)* keyword_item ::= identifier "=" expression
一个可选项为在位置和关键字参数后加上逗号而不影响语义。
此原型必须求值为一个可调用对象(用户定义的函数,内置函数,内置对象的方法,类对象,类实例的方法以及任何具有 __call__() 方法的对象都是可调用对象)。 所有参数表达式将在尝试调用前被求值。
如果存在关键字参数,它们会先通过以下操作被转换为位置参数。 首先,为正式参数创建一个未填充空位的例表。 如果有 N 个位置参数,则它们会被放入前 N 个空位。 然后,对于每个关键字参数,使用标识符来确定其对应的空位(如果标识符与第一个正式参数名相同则使用第一个空位,依此类推)。 如果空位已被填充,则会引发 TypeError 异常。 否则,将参数值放入空位,进行填充(即使表达式为 None,它也会填充空位)。
当所有参数处理完毕时,尚未填充的空位将用来自函数定义的相应默认值来填充。 (函数一旦被定义,其默认值就会被计算;因此,当列表或字典这类可变对象被用作默认值时将会被所有未指定相应空位参数值的调用所共享;这种情况通常应当被避免。) 如果任何一个未填充空位没有指定默认值,则会引发 TypeError 异常。 在其他情况下,已填充空位的列表会被作为调用的参数列表。
CPython 实现细节: 某些实现可能提供位置参数没有名称的内置函数,即使它们在文档说明的场合下有“命名”,因此不能以关键字形式提供参数。 在 CPython 中,以 C 编写并使用 PyArg_ParseTuple() 来解析其参数的函数实现就属于这种情况。
如果存在比正式参数空位多的位置参数,将会引发 TypeError 异常,除非有一个正式参数使用了 *identifier 句法;在此情况下,该正式参数将接受一个包含了多余位置参数的元组(如果没有多余位置参数则为一个空元组)。
如果任何关键字参数没有与之对应的正式参数名称,将会引发 TypeError 异常,除非有一个正式参数使用了 **identifier 句法,该正式参数将接受一个包含了多余关键字参数的字典(使用关键字作为键而参数值作为与键对应的值),如果没有多余关键字参数则为一个(新的)空字典。
如果函数调用中出现了 *expression 句法,expression 必须求值为一个 iterable。 来自该可迭代对象的元素会被当作是额外的位置参数。 对于 f(x1, x2, *y, x3, x4) 调用,如果 y 求值为一个序列 y1, …, yM,则它就等价于一个带有 M+4 个位置参数 x1, x2, y1, …, yM, x3, x4 的调用。
这样做的一个后果是虽然 *expression 句法可能出现于显式的关键字参数 之后,但它会在关键字参数(以及任何 **expression 参数) 之前被处理。 因此:
>>>def f(a, b): ... print(a, b) ... >>>f(b=1, *(2,)) 2 1 >>>f(a=1, *(2,)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() got multiple values for keyword argument 'a' >>>f(1, *(2,)) 1 2
在同一个调用中同时使用关键字参数和 *expression 语句并不常见,因此实际上这样的混淆不会发生。如果函数调用中出现了 **expression,则 expression 必须求值为一个 mapping,其内容会被当作是额外的关键字参数。 如果一个形参与一个已给定值关键字相匹配(通过显式的关键字参数,或通过另一个解包),则会引发 TypeError 异常。
当使用 **expression 时,该映射中的每个键都必须为字符串。 该映射中的每个值将被赋值给名称与键相同的适用于关键字赋值的第一个正式形参。 键名不需要是 Python 标识符(例如 “max-temp °F” 也是可接受的,但它将不能与可被声明的任何正式形参相匹配)。 如果键值对未与某个正式形参相匹配则将被 ** 形参所收集,或者如果没有此形参,则会引发 TypeError 异常。
使用 *identifier 或 **identifier 句法的正式参数不能被用作位置参数空位或关键字参数名称。
在 3.5 版更改:函数调用接受任意数量的 * 和 ** 拆包,位置参数可能跟在可迭代对象拆包 (*) 之后,而关键字参数可能跟在字典拆包 (**) 之后。
除非引发了异常,调用总是会有返回值,返回值也可能为 None。 返回值的计算方式取决于可调用对象的类型。
如果类型为:
1、用户自定义函数
函数的代码块会被执行,并向其传入参数列表。 代码块所做的第一件事是将正式形参绑定到对应参数;相关描述参见 函数定义 一节。 当代码块执行 return 语句时,由其指定函数调用的返回值。
2、内置函数或方法
具体结果依赖于解释器;有关内置函数和方法的描述参见 内置函数。
3、类对象
返回该类的一个新实例。
4、类实例方法
调用相应的用户自定义函数,向其传入的参数列表会比调用的参数列表多一项:该实例将成为第一个参数。
5、类实例
该类必须定义有 __call__() 方法;作用效果将等价于调用该方法。