Python是当下非常流行的编程语言,其赋值语句的多样性是其特色之一。除了常见的变量赋值,Python还支持许多其他赋值方式,包括多重赋值、增量赋值以及解包赋值等。本教程将进一步介绍Python中赋值语句的多种形式。
一、赋值语句示例
赋值语句用于将名称(重)绑定到特定值,以及修改属性或可变对象的成员项:
assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression) target_list ::= target ("," target)* [","] target ::= identifier | "(" [target_list] ")" | "[" [target_list] "]" | attributeref | subscription | slicing | "*" target
如想了解属性引用, 抽取和切片的句法定义,请参阅《Python原型》。
赋值语句会对指定的表达式列表进行求值(注意这可能为单一表达式或是由逗号分隔的列表,后者将产生一个元组)并将单一结果对象从左至右逐个赋值给目标列表。
对象赋值的目标对象可以包含于圆括号或方括号内,具体操作按以下方式递归地定义。
1、如果目标列表为后面不带逗号、可以包含于圆括号内的单一目标,则将对象赋值给该目标。
2、否则:
如果目标列表包含一个带有星号前缀的目标,这称为“加星”目标:则该对象至少必须为与目标列表项数减一相同项数的可迭代对象。 该可迭代对象前面的项将按从左至右的顺序被赋值给加星目标之前的目标。 该可迭代对象末尾的项将被赋值给加星目标之后的目标。 然后该可迭代对象中剩余项的列表将被赋值给加星目标(该列表可以为空);
- 否则:该对象必须为具有与目标列表相同项数的可迭代对象,这些项将按从左至右的顺序被赋值给对应的目标;
- 对象赋值给单个目标的操作按以下方式递归地定义。
3、如果目标为标识符(名称):
- 如果该名称未出现于当前代码块的 global 或 nonlocal 语句中:该名称将被绑定到当前局部命名空间的对象;
- 否则:该名称将被分别绑定到全局命名空间或由 nonlocal 所确定的外层命名空间的对象。
4、如果该名称已经被绑定则将被重新绑定。 这可能导致之前被绑定到该名称的对象的引用计数变为零,造成该对象进入释放过程并调用其析构器(如果存在)。
如果该对象为属性引用:引用中的原型表达式会被求值。 它应该产生一个具有可赋值属性的对象;否则将引发 TypeError。 该对象会被要求将可赋值对象赋值给指定的属性;如果它无法执行赋值,则会引发异常 (通常应为 AttributeError 但并不强制要求)。
注意:如果该对象为类实例并且属性引用在赋值运算符的两侧都出现,则右侧表达式 a.x 可以访问实例属性或(如果实例属性不存在)类属性。 左侧目标 a.x 将总是设定为实例属性,并在必要时创建该实例属性。 因此 a.x 的两次出现不一定指向相同的属性:如果右侧表达式指向一个类属性,则左侧会创建一个新的实例属性作为赋值的目标:
class Cls: x = 3 # class variable inst = Cls() inst.x = inst.x + 1 # writes inst.x as 4 leaving Cls.x as 3
5、如果目标为一个抽取项:引用中的原型表达式会被求值。 它应当产生一个可变序列对象(例如列表)或一个映射对象(例如字典)。 接下来,该抽取表达式会被求值。
6、如果目标为一个切片:引用中的原型表达式会被求值。 它应当产生一个可变序列对象(例如列表)。 被赋值对象应当是一个相同类型的序列对象。 接下来,下界与上界表达式如果存在的话将被求值;默认值分别为零和序列长度。 上下边界值应当为整数。
二、基本赋值
最基本的赋值语句是将一个值赋给一个变量。例如:
x = 10 y = "Hello, World!"
三、多重赋值
Python支持将多个值同时赋给多个变量,这就是多重赋值语句。例如:
x, y = 10, "Hello, World!"
四、增量赋值
增量赋值语句是一种简洁的赋值方式,它将变量的值增加或减少指定的数量。例如:
x += 5 # x = x + 5 x -= 3 # x = x - 3 x *= 2 # x = x * 2 x /= 4 # x = x / 4 x %= 2 # x = x % 2
五、解包赋值
解包赋值语句可以将一个序列(如列表、元组等)中的元素分别赋给多个变量。例如:
a, b, c = [1, 2, 3] # a = 1, b = 2, c = 3
六、链式赋值
链式赋值语句可以在一行代码中完成多个变量的赋值操作。例如:
x, y = y, x + y # x和y互换,并更新y的值为x+y
七、异常处理中的赋值
在异常处理中,可以使用赋值语句来处理异常对象。例如:
try: # some code that may raise an exception except Exception as e: # handle the exception by printing its message and traceback print("An error occurred:", e) import traceback; traceback.print_exc()
八、增强赋值语句
增强赋值语句就是在单个语句中将二元运算和赋值语句合为一体:
augmented_assignment_stmt ::= augtarget augop (expression_list | yield_expression) augtarget ::= identifier | attributeref | subscription | slicing augop ::= "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" | ">>=" | "<<=" | "&=" | "^=" | "|="
增强赋值语句将对目标和表达式列表求值(与普通赋值语句不同的是,前者不能为可迭代对象拆包),对两个操作数相应类型的赋值执行指定的二元运算,并将结果赋值给原始目标。 目标仅会被求值一次。
增强赋值语句例如 x += 1 可以改写为 x = x + 1 获得类似但并非完全等价的效果。 在增强赋值的版本中,x 仅会被求值一次。 而且,在可能的情况下,实际的运算是 原地 执行的,也就是说并不是创建一个新对象并将其赋值给目标,而是直接修改原对象。
不同于普通赋值,增强赋值会在对右手边求值 之前 对左手边求值。 例如,a[i] += f(x) 首先查找 a[i],然后对 f(x) 求值并执行加法操作,最后将结果写回到 a[i]。
除了在单个语句中赋值给元组和多个目标的例外情况,增强赋值语句的赋值操作处理方式与普通赋值相同。 类似地,除了可能存在 原地 操作行为的例外情况,增强赋值语句执行的二元运算也与普通二元运算相同。
九、带标注的赋值语句
标注赋值就是在单个语句中将变量或属性标注和可选的赋值语句合为一体:
annotated_assignment_stmt ::= augtarget ":" expression ["=" (starred_expression | yield_expression)]
对于将简单名称作为赋值目标的情况,如果是在类或模块作用域中,标注会被求值并存入一个特殊的类或模块属性 __annotations__ 中,这是一个将变量名称(如为私有会被移除)映射到被求值标注的字典。 此属性为可写并且在类或模块体开始执行时如果静态地发现标注就会自动创建;对于将表达式作为赋值目标的情况,如果是在类或模块作用域中,标注会被求值,但不会保存。
如果一个名称在函数作用域内被标注,则该名称为该作用域的局部变量。 标注绝不会在函数作用域内被求值和保存;如果存在右手边,带标注的赋值会在对标注求值之前(如果适用)执行实际的赋值。 如果用作表达式目标的右手边不存在,则解释器会对目标求值,但最后的 __setitem__() 或 __setattr__() 调用除外。