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 中增加了变量注释的语法,用于注释变量类型,包括类变量和实例变量:

1
2
3
4
5
6
7
8
from typing import Dict, List

primes: List[int] = []

captain: str # 无初值!

class Starship:
stats: Dict[str, int] = {}

1.2 序列赋值

1
2
3
4
nudge = 1
wink = 2
A, B = nudge, wink # 元组赋值
A, B
(1, 2)
1
2
[C, D] = [nudge, wink]    # 列表赋值
C, D
(1, 2)
1
2
nudge, wink = wink, nudge  # 元组:交换变量值
nudge, wink
(2, 1)

元组和列表赋值语句可以接受右侧是任何类型的序列,只要长度相等即可。

1
2
[a, b, c] = (1, 2, 3)       # 将元组值赋值给列表
a, c
(1, 3)
1
2
(a, b, c) = "ABC"            # 将字符串字符赋值给元组
a, c
('A', 'C')

1.3 扩展序列解包

一个带有单个星号的名称 *X,可以在赋值目标中使用,以指定对于序列的一个更为通用的匹配——一个列表赋给了带星号的名称,该列表收集了序列中没有赋值给其他名称的所有项。

扩展解包的实际应用

1
2
3
seq = [1, 2, 3, 4]
a, *b = seq # a 匹配第一项,b 匹配剩下的内容
a
1
1
b
[2, 3, 4]

带星号的名称可以出现在目标中的任何地方。

1
2
*a, b = seq
a
[1, 2, 3]
1
b
4

当带星号的名称出现在中间,它收集其他列出的名称之间的所有内容。

1
2
a, *b, c = seq
a
1
1
b
[2, 3]
1
c
4

和常规的序列赋值一样,扩展的序列解包语法对于任何序列类型都有效,不只是列表。

1
2
a, *b = 'spam'
a, b
('s', ['p', 'a', 'm'])
1
2
a, *b, c = 'spam'
a, b, c
('s', ['p', 'a'], 'm')
1
2
a, *b, c = range(4)
a, b, c
(0, [1, 2], 3)

这和分片的内在相似,但是不完全相同——一个序列解包赋值总是返回多个匹配项的一个列表,而分片把相同类型的一个序列作为分片的对象返回。

1
2
S = 'spam'
S[0], S[1:]
('s', 'pam')

边界情况
带星号的名称可能只匹配单个的项,但是,总是会向其赋值一个列表:

1
2
3
seq = [1, 2, 3, 4]
a, b, c, *d = seq
print(a, b, c, d)
1 2 3 [4]

如果没有剩下的内容可以匹配带星号的名称,它会赋值一个空的列表,不管该名称出现在哪里。

1
2
a, b, c, d, *e = seq
print(a, b, c, d, e)
1 2 3 4 []
1
2
a, b, *e, c, d = seq
print(a, b, c, d, e)
1 2 3 4 []

如果有多个带星号的名称,或者如果值少了而没有带星号的名称,以及如果带星号的名称自身没有编写到一个列表中,都将引发错误:

1
a, *b, c, *d = seq
  File "<ipython-input-25-619e26f566d5>", line 1
    a, *b, c, *d = seq
                      ^
SyntaxError: two starred expressions in assignment
1
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)
1
*a = seq
  File "<ipython-input-27-b87a23131697>", line 1
    *a = seq
            ^
SyntaxError: starred assignment target must be in a list or tuple
1
2
*a, = seq
a
[1, 2, 3, 4]

Python 3.5 中可以在函数调用时,允许任意多个解包操作:

1
print(*[1, 2, 3], *[3, 4])
1 2 3 3 4

还可用在表达式中:

1
[*range(4), 4]
[0, 1, 2, 3, 4]
1
2
3
4
# 拼接列表
list1 = [1, 2, 3]
list2 = range(3, 6)
[*list1, *list2]
[1, 2, 3, 3, 4, 5]
1
2
3
4
# 合并字典
a = {'a': 1, 'b': 2}
b = {'c': 3, 'd': 4}
{**a, **b}
{'a': 1, 'b': 2, 'c': 3, 'd': 4}

1.4 多目标赋值语句

直接把所有提供的变量名都赋值给右侧的对象。

1
2
a = b = c = 'spam'
a, b, c
('spam', 'spam', 'spam')

多目标赋值以及共享引用
这里只有一个对象,由三个变量共享(全部指向内存内同一对象)。这种行为对于不可变类型而言并没问题。

1
2
3
a = b = 0
b = b + 1
a, b
(0, 1)

在这里,修改 b 只会对 b 发生修改,因为数字不支持在原处的修改。只要赋值对象是不可变的,即使有一个以上的变量名使用该对象也无所谓。

当变量初始值设为空的可变对象时(诸如列表或字典),我们就得小心一点:

1
2
3
a = b = []
b.append(42)
a, b
([42], [42])

因为 a 和 b 引用相同的对象,通过 b 在原处附加值上去,通过 a 也会看见所有的效果。

为避免这个问题,要在单独的语句中初始化可变对象,以便分别执行独立地常量表达式来创建独立地空对象:

1
2
3
4
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

增强赋值语句使用于任何支持隐式二元表达式的类型。

用于字符串时,增强形式会改为执行合并运算。

1
2
3
S = 'spam'
S += 'SPAM' # 隐式合并
S
'spamSPAM'

增强赋值语句有三个优点:

  • 程序员输入减少。
  • 左侧只需计算一次。在完整形式 X = X + Y 中,X 出现两次,必须执行两次,因此,增强赋值语句通常执行得更快。
  • 优化技术会自动选择。对于支持原处修改的对象而言,增强形式会自动执行原处的修改运算,而不是相比来说速度更慢的复制。

增强赋值以及共享引用
“+=”对列表是做原处修改,不像“+”合并,总是生成新的对象。

就所有共享引用情况而言,只有其他变量名引用的对象被修改,其差别才可能体现出来:

1
2
3
4
L = [1, 2]
M = L # L 和 M 引用相同对象
L = L + [3, 4] # 合并创建一个新的对象
L, M # 改变 L,但没有改变 M
([1, 2, 3, 4], [1, 2])
1
2
3
4
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 表达式语句和在原处的修改

表达式语句通常用于执行可于原处修改列表的列表方法:

1
2
3
L = [1, 2]
L.append(3) # append 在原处修改
L
[1, 2, 3]
1
2
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 函数的应用

1
print()               # 显示空行
1
2
3
4
x = 'spam'
y = 99
z = ['eggs']
print(x, y, z) # 默认打印三个对象
spam 99 ['eggs']

print 中不需要把对象转换为字符串,而在文件写入方法中则需要这么做。

默认情况下,print 在打印的对象之间添加一个空格。给 sep 关键字参数发送一个空字符串,或者自己所选择的替代分隔符,以取消空格。

1
print(x, y, z, sep='')      # 抑制分隔符
spam99['eggs']
1
print(x, y, z, sep=',')     # 自定义分隔符
spam,99,['eggs']

默认情况下,print 添加一个末行字符来结束输出行,可以通过向 end 关键字参数传递一个空字符串来避免换行,或者传递一个自己的终止符。

1
print(x, y, z, end='')      # 抑制行终止符
spam 99 ['eggs']
1
print(x, y, z, end='');print(x, y, z)
spam 99 ['eggs']spam 99 ['eggs']
1
print(x, y, z, end='...\n')    # 自定义行终止符
spam 99 ['eggs']...

也可以组合关键字参数来指定分隔符和行末字符串。

1
print(x, y, z, sep='...', end='!\n')    # 多个关键字
spam...99...['eggs']!
1
print(x, y, z, end='!\n', sep='...')
spam...99...['eggs']!

使用 file 关键字,在单个打印的过程中,直接把文本打印到一个输出文件或者其他的可兼容对象。

1
print(x, y, z, sep='...', file=open('data.txt', 'w'))    # 打印到文件
1
print(open('data.txt').read())      # 显示文件文本
spam...99...['eggs']

print 操作提供的分隔符和行末选项只是为了方便起见。如果需要显示更具体的格式,提前构建一个更复杂的字符串或者在 print 自身之中使用字符串工具,并一次性打印该字符串。

1
2
text = '%s: %-.4f, %05d' % ('Result', 3.14159, 42)
print(text)
Result: 3.1416, 00042
1
print('%s: %-.4f, %05d' % ('Result', 3.14159, 42))
Result: 3.1416, 00042

3.2 打印流重定向

重定向输出流
print(X, Y)
等价于:

sys
1
sys.stdout.write(str(X) + ' ' + str(Y) + '\n')

可以把 sys.stdout 重新赋值给标准输出流以外的东西。
这种等效的方式提供了一种方法,可以让 print 语句将文字传送到其他地方。

1
2
import sys
sys.stdout = open('log.txt', 'a') # 重定向打印至一个文件
1
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 语句还是会继续打印到原始的输出流。

1
2
3
log = open('log.txt', 'a')
print(x, y, z, file=log) # 打印到文件对象
print(x, y, z) # 打印到原始输出流

这种 print 的扩展形式通常也用于把错误消息打印到标准错误流 sys.stderr。可以使用其文件 write 方法以及自行设置输出的格式,或者使用重定向语法打印。

1
2
import sys
sys.stderr.write(('Bad!' * 8) + '\n')
Bad!Bad!Bad!Bad!Bad!Bad!Bad!Bad!
1
print('Bad!' * 8, file=sys.stderr)
Bad!Bad!Bad!Bad!Bad!Bad!Bad!Bad!