4. 控制工具

除了介绍 while 语句,Python 一些别的我们遇到它们
4.1. if
语句

耳熟能详语句应当 if 语句
>>>

x = int(input("Please enter an integer: "))
Please enter an integer: 42

if x < 0:

x = 0

print('Negative changed to zero')

elif x == 0:

print('Zero')

elif x == 1:

print('Single')

else:

print('More')




多个 elif 部分,else 部分可选关键字 'elif' 'else if' 缩写用于避免过多缩进。if ... elif ... elif ... 序列可以当作其它语言 switch case 语句替代品

如果多个常量进行比较或者检查特定类型属性,match 语句有用详见 match 语句
4.2. for
语句

Python
for 语句 C Pascal 中的不同。Python for 语句迭代算术递增数值 Pascal),或是给予用户定义迭代步骤结束条件能力 C),而是列表字符串任意序列元素迭代它们序列出现顺序例如不是有意暗指什么):
>>>

#
度量一些字符串

words = ['cat', 'window', 'defenestrate']

for w in words:

print(w, len(w))


cat 3
window 6
defenestrate 12

正确迭代同时修改内容简单方法迭代副本或者创建

#
创建示例
users = {'Hans': 'active', 'Éléonore': 'inactive', '
': 'active'}

#
策略迭代副本
for user, status in users.copy().items():
if status == 'inactive':
del users[user]

#
策略创建
active_users = {}
for user, status in users.items():
if status == 'active':
active_users[user] = status

4.3. range()
函数

内置函数 range() 用于生成等差数列
>>>

for i in range(5):

print(i)


0
1
2
3
4

生成序列不会包括给定终止;range(10) 生成 10 ——长度 10 序列所有合法索引。range 可以 0 开始可以给定递增即使负数):
>>>

list(range(5, 10))
[5, 6, 7, 8, 9]

list(range(0, 10, 3))
[0, 3, 6, 9]

list(range(-10, -100, -30))
[-10, -40, -70]

索引迭代序列可以组合使用 range() len():
>>>

a = ['Mary', 'had', 'a', 'little', 'lamb']

for i in range(len(a)):

print(i, a[i])


0 Mary
1 had
2 a
3 little
4 lamb

不过大多数情况 enumerate() 函数方便详见 循环技巧

如果直接打印 range 发生意想不到事情
>>>

range(10)
range(0, 10)

range()
返回对象多方面列表行为一样其实列表不一样对象只有迭代返回期望列表没有真正生成含有全部列表从而节省空间

这种对象称为迭代对象 iterable,适合作为需要获取一系列函数程序构件参数。for 语句就是这样程序构件迭代对象作为参数函数例如 sum():
>>>

sum(range(4)) # 0 + 1 + 2 + 3
6

之后我们看到返回迭代对象迭代对象作为参数函数 数据结构 我们讨论 list() 细节
4.4. break
continue 语句

break
语句跳出最近一层 for while 循环:
>>>

for n in range(2, 10):

for x in range(2, n):

if n % x == 0:

print(f"{n} equals {x} * {n//x}")

break


4 equals 2 * 2
6 equals 2 * 3
8 equals 2 * 4
9 equals 3 * 3

continue
语句继续执行循环下一次迭代:
>>>

for num in range(2, 10):

if num % 2 == 0:

print(f"Found an even number {num}")

continue

print(f"Found an odd number {num}")


Found an even number 2
Found an odd number 3
Found an even number 4
Found an odd number 5
Found an even number 6
Found an odd number 7
Found an even number 8
Found an odd number 9

4.5.
循环 else 子句

for while 循环 break 语句可能对应 else 子句如果循环执行 break 情况结束,else 子句将会执行

for 循环,else 子句循环结束其他最后一次迭代之后执行 break 情况执行

while 循环循环条件变为执行

循环循环 break 终结 else 子句 不会 执行当然其他提前结束循环方式 return 或是引发异常跳过 else 子句执行

下面搜索质数 for 循环就是例子
>>>

for n in range(2, 10):

for x in range(2, n):

if n % x == 0:

print(n, 'equals', x, '*', n//x)

break

else:

#
循环到底找到因数

print(n, 'is a prime number')


2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

正确代码仔细其中 else 子句属于 for 循环 属于 if 语句。)

分析 else 子句一种方式想象对应循环 if。 循环执行运行一系列 if/if/if/else。 if 位于循环内部出现多次出现条件真的情况发生 break。 如果条件一直循环 else 子句执行

配合循环使用,else 子句 try 语句 else 子句 if 语句相应子句 try 语句 else 子句发生异常时运循环 else 子句发生 break 运行有关 try 语句异常详情参阅 异常处理
4.6. pass
语句

pass
语句执行任何动作语法需要语句程序执行任何动作可以使用语句例如
>>>

while True:

pass #
无限等待键盘中断 (Ctrl+C)


常用创建小的
>>>

class MyEmptyClass:

pass


pass
用作函数条件语句占位符保持抽象层次进行思考。pass 默默忽略
>>>

def initlog(*args):

pass #
记得实现这个


4.7. match
语句

match
语句接受表达式多个 case 一系列模式进行比较表面上 C、Java JavaScript(以及许多其他程序设计语言中的 switch 语句其实 Rust Haskell 中的模式匹配只有第一匹配模式执行并且可以提取组成部分序列元素对象属性变量

简单形式主语多个面值进行比较

def http_error(status):
match status:
case 400:
return "Bad request"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"

注意最后代码:“变量” _ 作为 通配符 必定匹配成功如果没有 case 匹配成功不会执行任何分支

可以 | (“”)多个面值组合模式

case 401 | 403 | 404:
return "Not allowed"

解包赋值模式用于绑定变量

# point
(x, y) 元组
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")

仔细学习代码第一模式面值视为前述面值模式扩展接下来模式结合面值变量变量 绑定 来自主语(point)模式捕获使概念解包赋值 (x, y) = point 类似

如果组织数据可以参数列表这种构造形式属性捕获变量

class Point:
def __init__(self, x, y):
self.x = x
self.y = y

def where_is(point):
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")

一些内置 dataclass)属性提供顺序此时可以使用位置参数自定义通过设置特殊属性 __match_args__,属性指定模式对应位置 ("x", "y"),以下模式相互等价属性 y 绑定变量 var):

Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)

建议这样阅读模式——通过视为赋值语句等号左边一种扩展形式理解各个变量为何。match 语句单一名称上面 var)赋值不会赋值点号名称 foo.bar)、属性上面 x= y=)通过其后 "(...)" 别的上面 Point)。

模式可以任意嵌套举例来说如果我们 Point 组成列表 Point 添加 __match_args__ 我们可以这样匹配

class Point:
__match_args__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y

match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")

我们可以模式添加 if 作为守卫子句如果守卫子句那么 match 继续尝试匹配下一个 case 注意捕获守卫子句求值

match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")

语句一些其它关键特性

解包赋值类似元组列表模式具有完全相同含义并且实际上匹配任意序列区别它们不能匹配迭代字符串

序列模式支持扩展解包:[x, y, *rest] (x, y, *rest) 相应解包赋值一样 * 名称可以 _,所以 (x, y, *_) 匹配至少序列不必绑定剩余

映射模式:{"bandwidth": b, "latency": l} 字典捕获 "bandwidth" "latency" 额外忽略一点序列模式不同。**rest 这样解包支持。( **_ 将会冗余允许使用。)

使用 as 关键字可以捕获模式

case (Point(x1, y1), Point(x2, y2) as p2): ...

输入中的第二元素捕获 p2 (只要输入包含序列

大多数字面相等比较但是对象 True、False None id 比较

模式可以使用具名常量它们必须作为点号名称出现防止它们解释用于捕获变量

from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'

color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))

match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")

详细说明示例参阅教程格式撰写 PEP 636。
4.8.
定义函数

下列代码创建可以输出限定数值斐波那契数列函数
>>>

def fib(n): #
打印小于 n 斐波那契数列

"""Print a Fibonacci series less than n."""

a, b = 0, 1

while a < n:

print(a, end=' ')

a, b = b, a+b

print()


#
现在调用我们定义函数

fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

定义 函数使用关键字 def,后跟函数括号列表函数语句一行开始并且必须缩进

函数第一语句字符串字符串就是文档字符串称为 docstring,详见 文档字符串利用文档字符串可以自动生成在线文档打印文档可以让开浏览代码直接查阅文档;Python 开发者最好养成代码加入文档字符串习惯

函数 执行 使用函数局部变量符号所有函数变量赋值存在局部符号引用变量首先局部符号表里查找变量然后外层函数局部符号全局符号最后内置名称符号因此尽管可以引用全局变量外层函数变量最好不要函数直接赋值除非 global 语句定义全局变量 nonlocal 语句定义外层函数变量)。

调用函数实际参数实参引入调用函数局部符号因此实参使用 调用 传递其中 始终对象 引用 不是对象)。 [1] 函数调用另外函数调用创建局部符号

函数定义当前符号函数函数对象关联在一起解释器函数指向对象作为用户自定义函数可以使用其他名称指向同一函数对象访问访函数
>>>

fib
<function fib at 10042ed0>

f = fib

f(100)
0 1 1 2 3 5 8 13 21 34 55 89

如果其他语言可能认为 fib 不是函数而是过程因为没有返回事实上即使没有 return 语句函数返回尽管这个可能相当无聊这个称为 None (内置名称)。 通常解释器屏蔽单独返回 None。 如果需要可以使用 print() 查看:
>>>

fib(0)

print(fib(0))
None

编写直接输出斐波那契数列运算结果而是返回运算结果列表函数非常简单
>>>

def fib2(n): #
返回斐波那契数组直到 n

"""Return a list containing the Fibonacci series up to n."""

result = []

a, b = 0, 1

while a < n:

result.append(a) #


a, b = b, a+b

return result


f100 = fib2(100) #
调用

f100 #
输出结果
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

引入一些 Python 功能

return
语句返回函数。return 语句不带表达式参数返回 None。函数执行完毕退出返回 None。

语句 result.append(a) 调用列表对象 result 方法方法属于对象函数名称 obj.methodname,其中 obj 对象可以表达式),methodname 对象类型定义方法名称不同类型定义不同方法不同类型方法可以使用相同名称不会产生歧义。 (使用 可以定义自己对象类型方法参见 。) 示例显示方法 append() 列表对象定义列表末尾添加元素等同 result = result + [a],效率

4.9.
函数定义详解

函数定义支持可变数量参数这里列出可以组合使用形式
4.9.1.
默认参数

参数指定默认非常有用方式调用函数可以使用定义参数例如

def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
reply = input(prompt)
if reply in {'y', 'ye', 'yes'}:
return True
if reply in {'n', 'no', 'nop', 'nope'}:
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)

函数可以以下方式调用

实参:ask_ok('Do you really want to quit?')

可选实参:ask_ok('OK to overwrite the file?', 2)

所有实参:ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

使用关键字 in ,用于确认序列是否包含

默认 定义 作用函数定义求值所以

i = 5

def f(arg=i):
print(arg)

i = 6
f()

输出 5。

重要警告默认计算一次默认列表字典实例可变对象产生规则不同结果例如下面函数累积后续调用传递参数

def f(a, L=[]):
L.append(a)
return L

print(f(1))
print(f(2))
print(f(3))

输出结果如下

[1]
[1, 2]
[1, 2, 3]

不想后续调用之间共享默认如下方式编写函数

def f(a, L=None):
if L is None:
L = []
L.append(a)
return L

4.9.2.
关键字参数

kwarg=value
形式 关键字参数 可以用于调用函数函数示例如下

def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")

函数接受参数(voltage)可选参数(state, action type)。函数下列方式调用

parrot(1000) # 1
位置参数
parrot(voltage=1000) # 1
关键字参数
parrot(voltage=1000000, action='VOOOOOM') # 2
关键字参数
parrot(action='VOOOOOM', voltage=1000000) # 2
关键字参数
parrot('a million', 'bereft of life', 'jump') # 3
位置参数
parrot('a thousand', state='pushing up the daisies') # 1
位置参数,1 关键字参数

以下调用函数方式无效

parrot() #
缺失必需参数
parrot(voltage=5.0, 'dead') #
关键字参数存在关键字参数
parrot(110, voltage=220) #
同一参数重复
parrot(actor='John Cleese') #
未知关键字参数

函数调用关键字参数必须位置参数后面所有传递关键字参数必须匹配函数接受参数比如,actor 不是函数 parrot 有效参数),关键字参数顺序并不重要包括参数,(比如,parrot(voltage=1000) 有效)。不能同一参数多次赋值下面就是因此限制失败例子
>>>

def function(a):

pass


function(0, a=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: function() got multiple values for argument 'a'

最后 **name 形式接收字典详见 映射类型 --- dict),字典包含函数定义对应之外所有关键字参数。**name 可以 *name 小节介绍组合使用(*name 必须 **name 前面), *name 接收 元组元组包含列表之外位置参数例如可以定义下面这样函数

def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
for kw in keywords:
print(kw, ":", keywords[kw])

函数可以如下方式调用

cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")

输出结果如下

-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
shopkeeper : Michael Palin
client : John Cleese
sketch : Cheese Shop Sketch

注意关键字参数输出结果中的顺序调用函数顺序一致
4.9.3.
特殊参数

默认情况参数可以位置关键字传递 Python 函数为了代码易读高效最好限制参数传递方式这样开发者查看函数定义即可确定参数位置位置关键字还是关键字传递

函数定义如下

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
|
位置关键字 |
| -
关键字
--
位置

/
* 可选这些符号表明如何参数传递函数位置位置关键字关键字关键字形叫作命名
4.9.3.1.
位置关键字参数

函数定义使用 / * 参数可以位置关键字传递函数
4.9.3.2.
位置参数

此处介绍一些细节特定可以标记 位置位置 顺序重要这些不能关键字传递位置 / (斜杠。/ 用于逻辑分割位置参与其它如果函数定义没有 /,表示没有位置

/
可以 位置关键字 关键字
4.9.3.3.
关键字参数

标记 关键字表明必须关键字参数形式传递参数列表第一 关键字 添加 *。
4.9.3.4.
函数示例

请看下面函数定义示例注意 / * 标记
>>>

def standard_arg(arg):

print(arg)


def pos_only_arg(arg, /):

print(arg)


def kwd_only_arg(*, arg):

print(arg)


def combined_example(pos_only, /, standard, *, kwd_only):

print(pos_only, standard, kwd_only)

第一函数定义 standard_arg 常见形式对调方式没有任何限制可以位置可以关键字传递参数
>>>

standard_arg(2)
2

standard_arg(arg=2)
2

第二函数 pos_only_arg 函数定义 /,使用位置
>>>

pos_only_arg(1)
1

pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'

函数 kwd_only_arg 函数定义通过 * 指明那样允许关键字参数
>>>

kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

kwd_only_arg(arg=3)
3

最后函数同一函数定义使用全部调用惯例
>>>

combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given

combined_example(1, 2, kwd_only=3)
1 2 3

combined_example(1, standard=2, kwd_only=3)
1 2 3

combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'

下面函数定义,kwds name 当作因此可能位置参数 name 产生潜在冲突

def foo(name, **kwds):
return 'name' in kwds

调用函数不可能返回 True,因为关键字 'name' 第一绑定例如
>>>

foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'


加上 / (位置参数可以此时函数定义 name 当作位置参数,'name' 可以作为关键字参数
>>>

def foo(name, /, **kwds):

return 'name' in kwds


foo(1, **{'name': 2})
True

换句话说位置名称可以 **kwds 使用产生歧义
4.9.3.5.
小结

以下决定哪些可以用于函数定义

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

说明

使用位置可以用户无法使用没有实际意义强制调用函数实参顺序同时接收位置关键字这种方式有用

实际意义名称可以函数定义易理解阻止用户依赖传递实参位置使用关键字

对于 API,使用位置可以防止未来修改造成破坏性 API 变动

4.9.4.
任意实参列表

调用函数使用任意数量实参最少选项这些实参包含元组详见 元组序列 )。可变数量实参之前可能若干普通参数

def write_multiple_items(file, separator, *args):
file.write(separator.join(args))

variadic
参数用于采集传递函数所有剩余参数因此它们通常列表末尾。*args 任何形式参数只能关键字参数只能用作关键字参数不能用作位置参数
>>>

def concat(*args, sep="/"):

return sep.join(args)


concat("earth", "mars", "venus")
'earth/mars/venus'

concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'

4.9.5.
解包实参列表

函数调用要求独立位置参数实参列表元组执行相反操作例如内置 range() 函数要求独立 start stop 实参如果这些参数不是独立调用函数 * 操作符实参列表元组解包出来
>>>

list(range(3, 6)) #
附带参数正常调用
[3, 4, 5]

args = [3, 6]

list(range(*args)) #
附带列表解包参数调用
[3, 4, 5]

同样字典可以 ** 操作符传递关键字参数
>>>

def parrot(voltage, state='a stiff', action='voom'):

print("-- This parrot wouldn't", action, end=' ')

print("if you put", voltage, "volts through it.", end=' ')

print("E's", state, "!")


d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}

parrot(**d)
-- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !

4.9.6. Lambda
表达式

lambda
关键字用于创建小巧匿名函数。lambda a, b: a+b 函数返回参数。Lambda 函数用于任何需要函数对象地方语法匿名函数只能单个表达式语义只是常规函数定义语法糖嵌套函数定义一样,lambda 函数可以引用包含作用中的变量
>>>

def make_incrementor(n):

return lambda x: x + n


f = make_incrementor(42)

f(0)
42

f(1)
43

lambda 表达式返回函数可以匿名函数用作传递实参
>>>

pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]

pairs.sort(key=lambda pair: pair[1])

pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]

4.9.7.
文档字符串

以下文档字符串内容格式约定

第一对象用途简短摘要保持简洁不要这里说明对象类型因为通过其他方式获取这些信息除非名称碰巧描述函数操作动词)。一行大写字母开头句点结尾

文档字符串第二空白视觉上将摘要其余描述分开后面包含若干段落描述对象调用约定副作用

Python
解析不会删除 Python 字符串面值缩进因此文档处理工具必要删除缩进操作遵循以下约定文档字符串第一 之后 第一非空决定整个文档字符串缩进第一通常字符串开头引号相邻缩进字符串并不明显因此不能第一缩进),然后删除字符串所有开头缩进等价空白不能缩进如果出现缩进删除这些所有前导空白转化制表通常 8 空格),测试空白

下面文档字符串例子
>>>

def my_function():

"""Do nothing, but document it.


No, really, it doesn't do anything.

"""

pass


print(my_function.__doc__)
Do nothing, but document it.

No, really, it doesn't do anything.

4.9.8.
函数注解

函数注解 可选用户自定义函数类型元数据完整信息详见 PEP 3107 PEP 484 )。

标注 字典形式存放函数 __annotations__ 属性函数其他部分没有影响标注定义方式后加冒号后面求值标注表达式返回标注定义方式组合符号 ->,后面表达式这样位于列表表示 def 语句结束冒号下面示例必须参数可选关键字参数以及返回带有相应标注:
>>>

def f(ham: str, eggs: str = 'eggs') -> str:

print("Annotations:", f.__annotations__)

print("Arguments:", ham, eggs)

return ham + ' and ' + eggs


f('spam')
Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
Arguments: spam eggs
'spam and eggs'

4.10.
小插曲编码风格

现在将要复杂 Python 代码时候讨论一下 代码风格 大多数语言不同风格编写准确格式化);有些其他具有可读性其他轻松阅读代码总是主意采用一种编码风格帮助

Python
项目大多遵循 PEP 8 风格指南推行编码风格易于阅读赏心悦目。Python 开发者抽时间悉心研读以下提案中的核心要点

缩进 4 空格不要制表

4
空格缩进更深嵌套缩进阅读之间折中方案制表引起混乱最好

换行一行超过 79 字符

这样换行阅读体验便于显示器并排阅读多个代码文件

分隔函数函数较大代码

最好释放单独一行

使用文档字符串

运算前后逗号空格不要直接括号使用: a = f(1, 2) + g(3, 4)。

函数命名一致惯例命名 UpperCamelCase,命名函数方法 lowercase_with_underscores。命名方法第一参数总是 self (方法详见 初探)。

编写用于国际环境代码不要生僻编码。Python 默认 UTF-8 ASCII 可以胜任各种情况

同理就算阅读维护代码可能不要标识使用 ASCII 字符

备注
[1]

实际上对象引用调用 这种说法因为传递可变对象调用发现做出任何更改插入列表元素)。