说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 中的赋值语句的基本形式是在等号左边写赋值语句的目标。要赋值的对象位于右侧。左侧的目标可以是变量名或者元素。右侧的对象可以是任何会计算得到的对象表达式。
Python中的赋值语句用于将值赋给变量。主要有以下几种形式:
a = 'hello' # 基本形式
a, b = 'hello', 'world' # 元组赋值运算(位置性)
a, b = b, a # 交换对象
[a, b] = ['hello', 'world'] # 列表赋值运算(按位置)
(a, b) = 'hello', 'world' # 元组赋值运算(位置性)
a, b, c, d = 'good' # 序列赋值运算,通用性
a, b, c = [1, 2, 3] # 按顺序解包
a, *b = 'hello' # 扩展的序列解包(Python3.0)
a = b = 'hello' # 多目标赋值运算(链式赋值)
a += 1 # 增强赋值运算(相当于 a = a + 1)
a:int = 1 # 带标注的赋值语句
a = 1 + 1 # 计算结果赋值
# 对象相关
obj.a = 123 # 属性赋值
obj['name'] = 'Tom' # 抽取赋值
obj[1] = 'hello' # 索引赋值
obj[1:3] = 'hello', 'world' # 切片赋值
# 生成器
a = yield b
a = yield from b
注意,以下是错误的:
a, b, c = 3 # 3 不能解包
a = (b = 1 + 2) # 赋值语句不能再赋值
a + b = 10 # 标识符号相加不能赋值
a = 2b # 没有这个语法,应该 2*b
2a = b + 1 # 同上
最简单的赋值形式是直接为变量赋一个值:
x = 5
这会将数字5赋给变量x。
可以同时为多个变量赋值:
x, y, z = 1, 2, 3
这会将1赋给x,2赋给y,3赋给z。
可以将一个变量赋值给另一个变量:
x = y = z = 1
这等同于:
y = 1
z = 1
x = 1
可以将序列或集合中的元素依次赋值给变量:
x, y, z = [1, 2, 3]
这会将[1, 2, 3]中的每个元素依次赋给 x, y, z。
可以使用一些增强的运算符进行赋值操作:
x += 1 # 等同于 x = x + 1
x -= 1 # 等同于 x = x - 1
x *= 2 # 等同于 x = x * 2
# 等等
这就完成了Python中最主要的赋值语句。充分利用它们可以使代码更简洁。
赋值语句用于将名称(重)绑定到特定值,以及修改属性或可变对象的成员项。赋值语句会对指定的表达式列表进行求值(注意这可能为单一表达式或是由逗号分隔的列表,后者将产生一个元组)并将单一结果对象从左至右逐个赋值给目标列表。
赋值是根据目标(列表)的格式递归地定义的。 当目标为一个可变对象(属性引用、抽取或切片)的组成部分时,该可变对象必须最终执行赋值并决定其有效性,如果赋值操作不可接受也可能引发异常。 各种类型可用的规则和引发的异常通过对象类型的定义给出。
对象赋值的目标对象可以包含于圆括号或方括号内,具体操作按以下方式递归地定义。
对象赋值给单个目标的操作按以下方式递归地定义。
class Cls:
x = 3 # class variable
inst = Cls()
inst.x = inst.x + 1 # writes inst.x as 4 leaving Cls.x as 3
此描述不一定作用于描述器属性,例如通过 property() 创建的特征属性。
如果目标为一个抽取项:引用中的原型表达式会被求值。 它应当产生一个可变序列对象(例如列表)或一个映射对象(例如字典)。 接下来,该抽取表达式会被求值。
如果原型为一个可变序列对象(例如列表),抽取应产生一个整数。 如其为负值,则再加上序列长度。 结果值必须为一个小于序列长度的非负整数,序列将把被赋值对象赋值给该整数指定索引号的项。 如果索引超出范围,将会引发 IndexError (给被抽取序列赋值不能向列表添加新项)。
如果主对象是映射对象(如字典),则下标的类型必须与映射的键类型兼容,然后要求映射创建一个键/值对,将下标映射到指定的对象。这可以用相同的键值替换现有的键值对,也可以插入新的键值对(如果不存在具有相同值的键)。
对于用户定义对象,会调用 __setitem__()
方法并附带适当的参数。
如果目标为一个切片:引用中的原型表达式会被求值。 它应当产生一个可变序列对象(例如列表)。 被赋值对象应当是一个相同类型的序列对象。 接下来,下界与上界表达式如果存在的话将被求值;默认值分别为零和序列长度。 上下边界值应当为整数。 如果某一边界为负值,则会加上序列长度。 求出的边界会被裁剪至介于零和序列长度的开区间中。 最后,将要求序列对象以被赋值序列的项替换该切片。 切片的长度可能与被赋值序列的长度不同,这会在目标序列允许的情况下改变目标序列的长度。
CPython 实现细节: 在当前实现中,目标的句法被当作与表达式的句法相同,无效的句法会在代码生成阶段被拒绝,导致不太详细的错误信息。
虽然赋值的定义意味着左手边与右手边的重叠是“同时”进行的(例如 a, b = b, a 会交换两个变量的值),但在赋值给变量的多项集 之内 的重叠是从左至右进行的,这有时会令人混淆。 例如,以下程序将会打印出 [0, 2]:
x = [0, 1]
i = 0
i, x[i] = 1, 2 # i is updated, then x[i] is updated
print(x)
增强赋值语句将对目标和表达式列表求值(与普通赋值语句不同的是,前者不能为可迭代对象拆包),对两个操作数相应类型的赋值执行指定的二元运算,并将结果赋值给原始目标。 目标仅会被求值一次。
增强赋值语句例如 x += 1 可以改写为 x = x + 1 获得类似但并非完全等价的效果。 在增强赋值的版本中,x 仅会被求值一次。 而且,在可能的情况下,实际的运算是 原地 执行的,也就是说并不是创建一个新对象并将其赋值给目标,而是直接修改原对象。
不同于普通赋值,增强赋值会在对右手边求值 之前 对左手边求值。 例如,a[i] += f(x) 首先查找 a[i],然后对 f(x) 求值并执行加法操作,最后将结果写回到 a[i]。
除了在单个语句中赋值给元组和多个目标的例外情况,增强赋值语句的赋值操作处理方式与普通赋值相同。 类似地,除了可能存在 原地 操作行为的例外情况,增强赋值语句执行的二元运算也与普通二元运算相同。
对于属性引用类目标,针对常规赋值的 关于类和实例属性的警告 也同样适用。
标注 赋值就是在单个语句中将变量或属性标注和可选的赋值语句合为一体。与正常情况的赋值语句不同,它只允许一个目标。
对于将简单名称作为赋值目标的情况,如果是在类或模块作用域中,标注会被求值并存入一个特殊的类或模块属性 __annotations__
中,这是一个将变量名称(如为私有会被移除)映射到被求值标注的字典。 此属性为可写并且在类或模块体开始执行时如果静态地发现标注就会自动创建。
对于将表达式作为赋值目标的情况,如果是在类或模块作用域中,标注会被求值,但不会保存。
如果一个名称在函数作用域内被标注,则该名称为该作用域的局部变量。 标注绝不会在函数作用域内被求值和保存。
如果存在右手边,带标注的赋值会在对标注求值之前(如果适用)执行实际的赋值。 如果用作表达式目标的右手边不存在,则解释器会对目标求值,但最后的 __setitem__()
或 setattr() 调用除外。
Python中的赋值语句在底层实现上涉及一些比较有趣的机制。我们来看一下它的具体工作原理:
x = 5
时,解释器会先在内存中分配一个整数对象5,然后让x指向这个对象。x = 5
会使整数对象5的引用计数加1,而del x
会使其引用计数减1。x = 5
会在当前命名空间绑定"x"和整数对象5。后续对x的访问会通过该字典找到对应的整数对象。这也是多个变量名可以指向一个对象的原理。y = x
时,实际上是将y指向了和x相同的对象,这叫浅拷贝。而深拷贝会重新创建一个一模一样的对象,完全断开与原对象的绑定关系。这就是Python赋值语句的一些关键实现机制。正确理解它们会有助于更好地运用Python。
在 Python 中,x, y = y, x
这种交换两个变量的值的写法是浅拷贝,不是深拷贝。
这种写法实现起来非常简单,就是将 x 的值赋给 y,将 y 的值赋给 x,两者的内存地址不变,只是值进行了交换。
举个例子:
x = [1, 2, 3]
y = [4, 5, 6]
x, y = y, x
print(x) # [4, 5, 6]
print(y) # [1, 2, 3]
这里 x 和 y 只是交换了对列表的引用,并没有创建任何新的列表或复制内容。因此这属于浅拷贝,x 和 y 指向的内存地址保持不变。
而深拷贝通常需要使用 copy 模块或 [:] 切片操作来实现对对象的递归复制,这样新的变量将指向一个完全不同的内存空间。
所以简单地交换两个变量的值只能实现浅拷贝。如果需要深拷贝,还需要额外的操作。
注意,解包赋值有时不能分开编写,如:
a, b = b, a + b
# vs
a = b
b = a + b
这两种方法不是等价的,我们测试下:
a, b = 0, 1
a, b = b, a + b
a, b
# (1, 1)
# vs
a, b = 0, 1
a = b
b = a + b
a, b
# (1, 2)
它们所赋值的结果是不一样的。因为后者中的 a + b 时的 a 的对象已经发生了变化。
更新时间:2024-06-15 16:40:18 标签:python 赋值 赋值语句