2-1-赋值-表达式和打印
1. 赋值语句
基本形式是等号左边写赋值语句的目标,要赋值的对象位于右侧。
- **赋值语句建立对象引用值。**赋值语句总是建立对象的引用值,而不是复制对象。因此,Python变量更像是指针,而不是数据存储区域。
- **变量名在首次赋值时会被创建。**一旦赋值了,每当这个变量名出现在表达式时,就会被其所引用的值取代。
- 变量名在引用前必须先赋值。
- **执行隐式赋值的一些操作。**模块导入、函数和类的定义、for循环变量以及函数参数全部都是隐式赋值运算。
1.1 赋值语句的形式
运算 | 解释 |
---|---|
spam = 'Spam' | 基本形式 |
spam, ham = 'yum', 'YUM' | 元组赋值运算(位置性) |
[spam, ham] = ['yum', 'YUM'] | 列表赋值运算(位置性) |
a, b, c, d = 'spam' | 序列赋值运算,通用性 |
a, *b = 'spam' | 扩展的序列解包 |
spam = ham = 'lunch' | 多目标赋值运算 |
spams += 42 | 增强赋值运算(相当于 spams = spams + 42) |
**元组及列表分解赋值:**当在“=”左边编写元组或列表时,Python会按照位置把右边的对象和左边的目标从左至右相配对。
**序列赋值语句:**在最新的Python版本中,元组和列表赋值语句已统一为序列赋值语句的实例——任何变量名的序列都可赋值给任何值的序列,而Python会按位置一次赋值一个元素。上表第四行中:a赋值为's',b赋值为'p'。
**扩展的序列解包:**上表第五行中,用右边的字符串的第一个字母来匹配a,用剩下的部分来匹配b:a赋值为's',b赋值为'pam'。
**多目标赋值:**Python赋值相同对象的引用值(最右边的对象)给左边的所有目标。上表第六行中变量名spam和ham两者都赋值成对相同的字符串对象'lunch'的引用。
**增强赋值语句:**一种以简洁的方式结合表达式和赋值语句的简写形式。Python中每个二元表达式运算符都有增强赋值语句。
Python 3.6 中增加了变量注释的语法,用于注释变量类型,包括类变量和实例变量:
from typing import Dict, List
primes: List[int] = []
captain: str # 无初值!
class Starship:
stats: Dict[str, int] = {}
1.2 序列赋值
nudge = 1
wink = 2
A, B = nudge, wink # 元组赋值
A, B
(1, 2)
[C, D] = [nudge, wink] # 列表赋值
C, D
(1, 2)
nudge, wink = wink, nudge # 元组:交换变量值
nudge, wink
(2, 1)
元组和列表赋值语句可以接受右侧是任何类型的序列,只要长度相等即可。
[a, b, c] = (1, 2, 3) # 将元组值赋值给列表
a, c
(1, 3)
(a, b, c) = "ABC" # 将字符串字符赋值给元组
a, c
('A', 'C')
1.3 扩展序列解包
一个带有单个星号的名称 *X
,可以在赋值目标中使用,以指定对于序列的一个更为通用的匹配——一个列表赋给了带星号的名称,该列表收集了序列中没有赋值给其他名称的所有项。
扩展解包的实际应用
seq = [1, 2, 3, 4]
a, *b = seq # a 匹配第一项,b 匹配剩下的内容
a
1
b
[2, 3, 4]
带星号的名称可以出现在目标中的任何地方。
*a, b = seq
a
[1, 2, 3]
b
4
当带星号的名称出现在中间,它收集其他列出的名称之间的所有内容。
a, *b, c = seq
a
1
b
[2, 3]
c
4
和常规的序列赋值一样,扩展的序列解包语法对于任何序列类型都有效,不只是列表。
a, *b = 'spam'
a, b
('s', ['p', 'a', 'm'])
a, *b, c = 'spam'
a, b, c
('s', ['p', 'a'], 'm')
a, *b, c = range(4)
a, b, c
(0, [1, 2], 3)
这和分片的内在相似,但是不完全相同——一个序列解包赋值总是返回多个匹配项的一个列表,而分片把相同类型的一个序列作为分片的对象返回。
S = 'spam'
S[0], S[1:]
('s', 'pam')
边界情况
带星号的名称可能只匹配单个的项,但是,总是会向其赋值一个列表:
seq = [1, 2, 3, 4]
a, b, c, *d = seq
print(a, b, c, d)
1 2 3 [4]
如果没有剩下的内容可以匹配带星号的名称,它会赋值一个空的列表,不管该名称出现在哪里。
a, b, c, d, *e = seq
print(a, b, c, d, e)
1 2 3 4 []
a, b, *e, c, d = seq
print(a, b, c, d, e)
1 2 3 4 []
如果有多个带星号的名称,或者如果值少了而没有带星号的名称,以及如果带星号的名称自身没有编写到一个列表中,都将引发错误:
a, *b, c, *d = seq
File "<ipython-input-25-619e26f566d5>", line 1
a, *b, c, *d = seq
^
SyntaxError: two starred expressions in assignment
a, b = seq
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-26-27a5f7809a98> in <module>()
----> 1 a, b = seq
ValueError: too many values to unpack (expected 2)
*a = seq
File "<ipython-input-27-b87a23131697>", line 1
*a = seq
^
SyntaxError: starred assignment target must be in a list or tuple
*a, = seq
a
[1, 2, 3, 4]
Python 3.5 中可以在函数调用时,允许任意多个解包操作:
print(*[1, 2, 3], *[3, 4])
1 2 3 3 4
还可用在表达式中:
[*range(4), 4]
[0, 1, 2, 3, 4]
# 拼接列表
list1 = [1, 2, 3]
list2 = range(3, 6)
[*list1, *list2]
[1, 2, 3, 3, 4, 5]
# 合并字典
a = {'a': 1, 'b': 2}
b = {'c': 3, 'd': 4}
{**a, **b}
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
1.4 多目标赋值语句
直接把所有提供的变量名都赋值给右侧的对象。
a = b = c = 'spam'
a, b, c
('spam', 'spam', 'spam')
多目标赋值以及共享引用
这里只有一个对象,由三个变量共享(全部指向内存内同一对象)。这种行为对于不可变类型而言并没问题。
a = b = 0
b = b + 1
a, b
(0, 1)
在这里,修改 b 只会对 b 发生修改,因为数字不支持在原处的修改。只要赋值对象是不可变的,即使有一个以上的变量名使用该对象也无所谓。
当变量初始值设为空的可变对象时(诸如列表或字典),我们就得小心一点:
a = b = []
b.append(42)
a, b
([42], [42])
因为 a 和 b 引用相同的对象,通过 b 在原处附加值上去,通过 a 也会看见所有的效果。
为避免这个问题,要在单独的语句中初始化可变对象,以便分别执行独立地常量表达式来创建独立地空对象:
a = [] # a, b = [], [] 具有相同效果
b = []
b.append(42)
a, b
([], [42])
1.5 增强赋值语句
X += Y
X &= Y
X -= Y
X |= Y
X *= Y
X ^= Y
X /=Y
X >>= Y
X %= Y
X <<= Y
X **= Y
X //= Y
增强赋值语句使用于任何支持隐式二元表达式的类型。
用于字符串时,增强形式会改为执行合并运算。
S = 'spam'
S += 'SPAM' # 隐式合并
S
'spamSPAM'
增强赋值语句有三个优点:
- 程序员输入减少。
- 左侧只需计算一次。在完整形式 X = X + Y 中,X 出现两次,必须执行两次,因此,增强赋值语句通常执行得更快。
- 优化技术会自动选择。对于支持原处修改的对象而言,增强形式会自动执行原处的修改运算,而不是相比来说速度更慢的复制。
增强赋值以及共享引用
“+=”对列表是做原处修改,不像“+”合并,总是生成新的对象。
就所有共享引用情况而言,只有其他变量名引用的对象被修改,其差别才可能体现出来:
L = [1, 2]
M = L # L 和 M 引用相同对象
L = L + [3, 4] # 合并创建一个新的对象
L, M # 改变 L,但没有改变 M
([1, 2, 3, 4], [1, 2])
L = [1, 2]
M = L
L += [3, 4] # += 会自动调用 extend 方法
L, M # M 也在原处修改
([1, 2, 3, 4], [1, 2, 3, 4])
1.6 变量命名规则
语法:(下划线或字母)+(任意数目的字母、数字或下划线)
区分大小写
禁止使用保留字
命名惯例
- 以单一下划线开头的变量名(
_X
)不会被from module import *
语句导入。 - 前后各有两个下划线的变量名(
__X__
)是系统定义的变量名,对解释器有特殊意义。 - 以两下划线开头、但结尾没有两个下划线的变量名(
__X
)是类的本地(“压缩”)变量。 - 通过交互模式运行时,只有单个下划线的变量名(
_
)会保存最后表达式的结果。
Python 程序员通常会遵循各种其他惯例,例如类变量名通常以一个大写字母开头,而模块变量名以小写字母开头。
2. 表达式语句
在 Python 中,可以使用表达式作为语句(本身只占一行)。
表达式结果不会存储,只有当表达式工作并作为附加的效果,这样才有意义。
通常在两种情况下表达式用作语句:
- 调用函数和方法
- 在交互模式提示符下打印值
常见 Python 表达式语句
运算 | 解释 |
---|---|
spam(eggs, ham) | 函数调用 |
spam.ham(eggs) | 方法调用 |
spam | 在交互模式解释器内打印变量 |
print(a, b, c, sep='') | Python 3.X "中的打印操作 |
yield x ** 2 | 产生表达式的语句 |
2.1 表达式语句和在原处的修改
表达式语句通常用于执行可于原处修改列表的列表方法:
L = [1, 2]
L.append(3) # append 在原处修改
L
[1, 2, 3]
L = L.append(4) # append 返回 None,而不是 L
print(L)
None
对列表调用 append、sort 或 reverse 这类在原处修改的运算,一定是对列表做原处的修改,但这些方法在列表修改后并不会把列表返回。它们返回的是 None 对象。
3. 打印操作
打印是把一个或多个对象转换为其文本表达形式,然后发送给标准输出或另一个类似文件的流。
3.1 print 函数
print 内置函数通常在其自身的一行中调用,但是它不会返回我们所关心的任何值(返回 None)。
调用格式print([object, …][, sep=' '][, end='\n'][, file=sys.stdout][, flush=False])
方括号中的项是可选的,函数把字符串 sep 所分隔开的一个或多个对象的文本表示,后面跟着的字符串 end,都打印到流 file 中,在每个 flush 中是否清楚缓存输出。
- sep 是在每个对象的文本之间插入的一个字符串,如果没有传递的话,它默认是一个单个的空格;传递一个空字符串将会抑制分隔符。
- end 是添加在打印文本末尾的一个字符串,如果没有传递的话,它默认是一个 \n 换行符。传递一个空字符串将会避免在打印文本的末尾移动到下一个输入行——下一个print将会保持添加到当前输出行的末尾。
- file 指定了文本将要发送到的文件、标准流或者其他类似文件的对象;如果没有传递的话,它默认是 sys.stdout。带有一个类似文件的 write(string) 方法的任何对象都可以传递,但真正的文件应该已经为了输出而打开。
- flush 默认是 False。它允许打印命令将其文本立即通过输出流写入任意等待的接收器。通常,打印的输出是否缓存在内存中取决于 file,传递一个真值给 flush 将强制刷新流。
print 函数的应用
print() # 显示空行
x = 'spam'
y = 99
z = ['eggs']
print(x, y, z) # 默认打印三个对象
spam 99 ['eggs']
print 中不需要把对象转换为字符串,而在文件写入方法中则需要这么做。
默认情况下,print 在打印的对象之间添加一个空格。给 sep 关键字参数发送一个空字符串,或者自己所选择的替代分隔符,以取消空格。
print(x, y, z, sep='') # 抑制分隔符
spam99['eggs']
print(x, y, z, sep=',') # 自定义分隔符
spam,99,['eggs']
默认情况下,print 添加一个末行字符来结束输出行,可以通过向 end 关键字参数传递一个空字符串来避免换行,或者传递一个自己的终止符。
print(x, y, z, end='') # 抑制行终止符
spam 99 ['eggs']
print(x, y, z, end='');print(x, y, z)
spam 99 ['eggs']spam 99 ['eggs']
print(x, y, z, end='...\n') # 自定义行终止符
spam 99 ['eggs']...
也可以组合关键字参数来指定分隔符和行末字符串。
print(x, y, z, sep='...', end='!\n') # 多个关键字
spam...99...['eggs']!
print(x, y, z, end='!\n', sep='...')
spam...99...['eggs']!
使用 file 关键字,在单个打印的过程中,直接把文本打印到一个输出文件或者其他的可兼容对象。
print(x, y, z, sep='...', file=open('data.txt', 'w')) # 打印到文件
print(open('data.txt').read()) # 显示文件文本
spam...99...['eggs']
print 操作提供的分隔符和行末选项只是为了方便起见。如果需要显示更具体的格式,提前构建一个更复杂的字符串或者在 print 自身之中使用字符串工具,并一次性打印该字符串。
text = '%s: %-.4f, %05d' % ('Result', 3.14159, 42)
print(text)
Result: 3.1416, 00042
print('%s: %-.4f, %05d' % ('Result', 3.14159, 42))
Result: 3.1416, 00042
3.2 打印流重定向
重定向输出流print(X, Y)
等价于:
sys.stdout.write(str(X) + ' ' + str(Y) + '\n')
可以把 sys.stdout 重新赋值给标准输出流以外的东西。
这种等效的方式提供了一种方法,可以让 print 语句将文字传送到其他地方。
import sys
sys.stdout = open('log.txt', 'a') # 重定向打印至一个文件
print(x, y, z)
重设 sys.stdout 后,程序中任何地方的 print 语句都会讲文字写至文件 log.txt 的末尾,而不是原始的输出流。
进程中只有一个 sys 模块,通过这种方式赋值 sys.stdout 会把程序中任何地方的每个 print 都进行重定向。
甚至可以将 sys.stdout 重设为非文件的对象,只要该对象有 write 方法。当该对象是类时,打印的文字可以定位并通过任意方式进行处理。
自动化流重定向
通过赋值 sys.stdout 而将打印文字重定向的技巧实际上非常常用。但是有一个潜在的问题,就是没有直接的方式可以保存原始的输出流,在打印至文件后,可以切换回来。
在 Python 3.X 中,file 关键字允许一个单个的 print 调用将其文本发送给一个文件的 write 方法,而不用真正地重设 sys.stdout。因为这种重定向是暂时的,普通的 print 语句还是会继续打印到原始的输出流。
log = open('log.txt', 'a')
print(x, y, z, file=log) # 打印到文件对象
print(x, y, z) # 打印到原始输出流
这种 print 的扩展形式通常也用于把错误消息打印到标准错误流 sys.stderr。可以使用其文件 write 方法以及自行设置输出的格式,或者使用重定向语法打印。
import sys
sys.stderr.write(('Bad!' * 8) + '\n')
Bad!Bad!Bad!Bad!Bad!Bad!Bad!Bad!
print('Bad!' * 8, file=sys.stderr)
Bad!Bad!Bad!Bad!Bad!Bad!Bad!Bad!