Python|Python 3 快速入门 2 —— 流程控制与函数

本文假设你已经有一门面向对象编程语言基础,如Java等,且希望快速了解并使用Python语言。本文对重点语法和数据结构以及用法进行详细说明,同时对一些难以理解的点进行了图解,以便大家快速入门。一些较偏的知识点在大家入门以后根据实际需要再查询官方文档即可,学习时切忌胡子眉毛一把抓。同时,一定要跟着示例多动手写代码。学习一门新语言时推荐大家同时去刷leetcode,一来可以快速熟悉新语言的使用,二来也为今后找工作奠定基础。推荐直接在网页上刷leetcode,因为面试的时候一般会让你直接在网页编写代码。leetcode刷题路径可以按我推荐的方式去刷。以下代码中,以 >>>... 开头的行是交互模式下的代码部分,>?开头的行是交互模式下的输入,其他行是输出。python代码中使用 #开启行注释。
编程前菜

>>> a, b = 0, 1 ... while a < 10: ...print(a, end=', ') ...a, b = b, a+b# 多重赋值,右表达式在赋值前就已经求值了 ... 0, 1, 1, 2, 3, 5, 8,# Fibonacci series

如上为典型的python代码块,python中的代码行无需以; 结尾,控制块以缩进标识开始和结束。python中的比较操作符与CJava类似,需要注意的是:==比较两个引用所指对象的值是否相同,要比较两个引用是否指向同一个对象需要使用is关键字,也可以通过id方法查看两个引用所指对象的id是否相同来判断。
>>> a = [1, 2, 3] ... b = [1, 2, 3] ... print('a = b:', a == b) a = b: True# a, b 中元素的 个数相同 且 值依次相等>>> a is b False# a, b 没指向同一个对象>>> c = a >>> c is a# a, c 指向同一个对象 True >>> id(c) 2827535985280 >>> id(a) 2827535985280# c, a 所指对象id相同,故指向同一对象

流程控制 if 语句
Python中的if语句包含:ifelif以及else子句,elif等同于CJava中的else if:
>>> x = int(input("input a digit: ")) ... if a < 0: ...print("Negative") ... elif a == 0: ...print("Zero") ... else: ...print("Positive") ... input a digit: >? 10 Positive

for 和 while 语句
Python中的for语句通常用来遍历列表和字符串等容器,它不能像CJava中那样通过在for语句中添加条件判断和变量递增规则来控制for循环的次数:
>>> digits = [1, 2, 3] ... for x in digits:# 遍历list ...print(x, end=", ") ... 1, 2, 3, >>> s = "python" ... for ch in s:# 遍历字符串 ...print(ch, end=" ") ... p y t h o n

由于Pythonfor语句的这种“缺陷”,我们如何像CJava那样在for语句中利于下标去遍历list呢?首先我们可以利用while循环:
# while中根据下标遍历list >>> digits = [1, 2, 3] >>> i = 0 ... while i < len(digits): ...print(digits[i], end=" ") ...i = i + 1 ... 1 2 3 # while中根据下标修改list >>> digits = [1, 2, 3] >>> i = 0 ... while i < len(digits): ...if digits[i] == 2: ...digits[i] = 8 ...i = i + 1 ... >>> print(digits) [1, 8, 3]

其次,我们还可以利用range()函数和enumerate()函数:
# 使用range,以0为起点,len为终点(不包含),以1为步长创建下标序列 >>> digits = [1, 2, 3] ... for i in range(len(digits)): ...print(digits[i], end=" ") ... 1 2 3 >>> digits = [1, 2, 3] ... for i in range(len(digits)): ...if digits[i] == 2: ...digits[i] = 8 ... print(digits) [1, 8, 3]# 使用enumerate,创建枚举序列,可以同时取出位置索引和对应的值 >>> digits = [1, 2, 3] ... for i, v in enumerate(digits): ...print(i, v) ... 0 1 1 2 2 3>>> digits = [1, 2, 3] ... for i, v in enumerate(digits): ...if v == 2: ...digits[i] = 8 ... print(digits) [1, 8, 3]

我们还可以使用for语句来遍历字典:
# 遍历字典对象的key >>> cities = {"chengdu": "A", "mianyang": "B", "guangyuan": "H"} ... for k in cities: ...print(k, end=" ") ... chengdu mianyang guangyuan # 遍历字典对象时同时根据key遍历value >>> cities = {"chengdu": "A", "mianyang": "B", "guangyuan": "H"} ... for k in cities: ...print(k, cities[k], sep=": ") ... chengdu: A mianyang: B guangyuan: H# 调用字典对象的items方法后可同时遍历key和value >>> cities = {"chengdu": "A", "mianyang": "B", "guangyuan": "H"} ... for k, v in cities.items(): ...print(k, v, sep=": ") ... chengdu: A mianyang: B guangyuan: H

注意:上诉for语句中申明变量在循环结束后依然存在,而列表、元组等推导表达式中则不会:
>>> for x in [1, 2, 3]: ...print(x) ... 1 2 3 >>> print(x) 3

由于一般不推荐在遍历数据集合时直接修改原数据集合来获取我们想要的数据集合,这样不安全且不够灵活。推荐在遍历原数据集合时根据条件创建一个新的数据集合,而这正是Python语言中for语句的强大之处。
列表推导式
列表推导式创建列表,列表推导式的方括号内包括:一个表达式,后面为一个 for 子句,然后是零个或多个 forif 子句。
# 创建 0 - 9 的平方列表 >>> [v**2 for v in range(10)] [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]# 验证列表推导式执行后for子句中的变量是否还存在 >>> [v**2 for v in range(10)] ... print(v) Traceback (most recent call last): File "", line 2, in NameError: name 'v' is not defined# 将两个列表中不相等的元素组合起来 >>> [(x, y) for x in [1, 2, 3] for y in [1, 2, 3] if x != y] [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)] # 等价于 >>> res = [] ... for x in [1, 2, 3]: ...for y in [1, 2, 3]: ...if x != y: ...res.append((x, y)) ... print(res) [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]

列表推导式的第一个表达式不但可以为复杂的表达式、函数,甚至可以为另一个列表推导式:
>>> matrix = [ ...[1, 2, 3], ...[4, 5, 6], ...[7, 8, 9] ... ] ... # 创建matrix的转置 >>> [[row[i] for row in matrix] for i in range(3)] [[1, 4, 7], [2, 5, 8], [3, 6, 9]] # 等价于 >>> res = [] ... for i in range(3): ...res.append([row[i] for row in matrix]) ... print(res) [[1, 4, 7], [2, 5, 8], [3, 6, 9]] # 又等价于 >>> res = [] ... for i in range(3): ...temp = [] ...for row in matrix: ...temp.append(row[i]) ...res.append(temp) ... print(res) [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

与列表类似,集合、字典也都支持推导式(元组不支持):
# 集合推导式 >>> { x**2 for x in [1, 2, 1, 4]} {16, 1, 4}# 字典推导式 >>> { x: x**2 for x in [1, 2, 1, 4]} {1: 1, 2: 4, 4: 16}

循环中的break、continue 与 else 语句
Python中的循环同样支持其他语言中的breakcontinue语句,此外还提供了一个else语句:for循环中,当可迭代对象中的元素全部循环完毕时,或 while循环的条件为假时,执行该子句;break语句终止循环时,不执行该子句。
# 可迭代对象中的元素全部循环完毕时执行else子句 >>> for x in []: ...print(x, end=" ") ... else: ...print("end") ... end>>> for x in range(3): ...print(x, end=" ") ... else: ...print("end") ... 0 1 2 end# 使用break语句终止循环则不执行else子句 >>> for x in range(3): ...print(x, end=" ") ...if x == 2: ...break ... else: ...print("end") ... 0 1 2# continue不影响else子句的 >>> for x in range(3): ...print(x, end=" ") ...if x == 2: ...continue ... else: ...print("end") ... 0 1 2 end# while条件为假时执行else子句 >>> x = 10 ... while x < 10: ...print(x) ... else: ...print("not in while") ... not in while

match语句(3.10开始支持)
match语句类似其他语言中的switch语句,常用来替代多个if语句:
>>> status = 404 ... match status: ...case 404:# 默认只匹配一个分支,不需要也不能加入其他语言中的break语句 ...print("Not Found!") ...case 500: ...print("Internal Error!") ...case _:# _ 类似于其他语言中的default ...print("Not Known!") ... Not Found!

当我们希望多个case匹配同样的结果时可以使用|
case 401 | 402 | 403: print("Not Allowed!")

match的强大之处在于可以从值中提取子部分 (序列元素或对象属性) 并赋值给变量:
>>> point = (1, 2) ... match point: ...case (0, 0): ...print("Origin") ...case (0, y): ...print("on Y axis, y =", y) ...case (x, 0): ...print("on X axis, x =", x) ...case (x, y): ...print("x =", x, "y =", y) ...case _: ...print("Not Valid!") ... x = 1 y = 2

【Python|Python 3 快速入门 2 —— 流程控制与函数】更多功能需要时查询文档即可:match 语句。
Python还支持pass 语句,该语句不执行任何操作。语法上需要一个语句,但程序不实际执行任何动作时,可以使用该语句。该语句可以用作函数或条件子句的占位符,以便让开发者聚焦更抽象的层次。
函数 想必大家对函数都有所了解,相比其他语言,Python中的函数支持更丰富的传参方式。函数定义以def关键字开头:
>>> def printFib(n): ..."""打印 Fibonacci 数列 ... ...:param n: 小于n ...:return: None ...""" ...a, b = 0, 1 ... ...while a < n: ...print(a, end=" ") ...a, b = b, a + b ... >>> printFib(10) 0 1 1 2 3 5 8 >>> print(printFib(10)) 0 1 1 2 3 5 8 None# 默认返回值# 通过help函数或函数的__doc__变量可以查看文档内容 >>> help(printFib) Help on function printFib in module __main__:printFib(n) 打印 Fibonacci 数列:param n: 小于n :return: None>>> print(printFib.__doc__) 打印 Fibonacci 数列:param n: 小于n :return: None

上诉代码块中""" ... """表示文档字符串,用来对函数用途以及函数参数、返回值进行说明。利用文档字符串可以自动生成在线文档或打印版文档,在代码中加入文档字符串是一个好习惯,详见文档字符串。在定义printFib函数时没有调用return语句显示返回值,但依然会默认返回None
参数默认值
在定义函数时,我们可以同时为参数指定默认值,被指定默认值的参数在调用时是可选的:
>>> def area(length, width = None): ..."""计算长方形面积 ... ...:param length: 长 ...:param width: 宽,默认为None;省略时计算以length为边长的正方形面积 ...:return: 长方形面积 ...""" ...if width is None: ...width = length ...return length * width ... >>> print(area(4, 3)) 12 >>> print(area(4)) 16

注意:形参的默认值只在函数定义时计算一次,如果参数的默认值是可变类型,那么函数的多次调用就可能会相互影响。例如下面的函数会累积后续调用时传递的参数:
>>> def f(a, l=[]): ...l.append(a) ...return l ... >>> print(f(1)) [1] >>> print(f(2)) [1, 2]# 推荐如下方式解决 >>> def f(a, l=None): ...if l is None: ...l = [] ...l.append(a) ...return l ... >>> print(f(1)) [1] >>> print(f(2)) [2]

关键字传参
默认情况下,Python中调用函数时可以按函数定义时形参的位置次序依次传入参数,也可以按关键字(形参名=形参值)的方式传入参数(无需按函数定义时形参的顺序传递),还可以两者混用,但关键字传参必须在位置传参之后:
# 还是使用前面一个小节(参数默认值)的 area 函数 # 关键字传参,未按定义顺序 >>> print(area(width=2, length=4)) 8# 位置传参和关键字传参混用 >>> print(area(8, width=4)) 32# 关键字传参在位置传参之前,报错 >>> print(area(length=1, 2)) File "", line 1 print(area(length=1, 2)) ^ SyntaxError: positional argument follows keyword argument

可变参数
在我们定义函数时,不确定调用函数时会传入多少个参数的情况下,可以使用可变参数来匹配,可变参数以 *开头,匹配的结果以元组的形式存放。比如:计算多个数的和或乘积:
>>> def calc(base, *args, operation="+"): ...match operation: ...case "+": ...for i in args: ...base += i ...return base ...case "*": ...for i in args: ...base *= i ...return base ...case _: ...return "error" ... # 传参时,10 匹配给base;1, 2, 3匹配给可变参数args;operation为默认值, # 想要覆盖 operation 默认值必须使用关键字传参,否则会匹配给可变参数 >>> print(calc(10, 1, 2, 3)) 16# 传参时,10 匹配给base;1, 2, 3匹配给可变参数;operation通过关键字传参改为* >>> print(calc(1, 1, 2, 3, operation="*")) 6# 关键字传参不能位于位置传参之前 >>> print(calc(base = 0, 1, 2, 3)) File "", line 1 print(calc(base = 0, 1, 2, 3)) ^ SyntaxError: positional argument follows keyword argument

关键字参数
通过前面的讲解,我们已经知道调用函数时可以通过关键字传参。当我们传递的关键字参数不能被函数中定义的形参完全匹配时,我们可以通过关键字参数来获取剩余未匹配的变量,关键字参数以**开头,匹配的结果以字典存放。
>>> def calc(base, *args, operation="+", **others): ...if len(others) > 0: ...print("invalid: more keyword arguments:", others) ...return None ...match operation: ...case "+": ...for i in args: ...base += i ...return base ...case "*": ...for i in args: ...base *= i ...return base ...case _: ...return "error" ...>>> print(calc(1, 1, 2, 3, operation="*")) 6>>> print(calc(1, 1, 2, 3, operation="*", otherOne = 12, otherTwo = 13)) invalid: more keyword arguments: {'otherOne': 12, 'otherTwo': 13} None

提示:位置参数必须在关键字参数之前。很容易理解,因为前面已经讲过:位置传参必须在关键字传参之前。
特殊参数/*
默认情况下,Python中调用函数时可以按函数定义时形参的位置次序依次传入参数,也可以按关键字(形参名=形参值)的方式传入参数(无需按函数定义时形参的顺序传递),还可以两者混用。为了让代码易读、高效,可以通过/*两个特殊参数限制调用函数时参数的传递方式:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ------------------------------- ||| |位置传参或关键字传参关键字传参 | -- 位置传参

由图易知:/以前的参数只能通过位置次序依次传参,/以后的参数可以通过位置次序或关键字的方式传参,*以后的参数只能通过关键字的方式传参。特殊参数可以两个同时出现,也可以只有一个,或者一个也没有(默认)。示例如下:
>>> def f(pos_only, /, standard, *, kwd_only): ...print(pos_only, standard, kwd_only) ... # 正确传参 >>> f(1, 2, kwd_only=3) 1 2 3 >>> f(1, standard=2, kwd_only=3) 1 2 3# 错误传参 >>> f(pos_only=1, 2, kwd_only=3) File "", line 1 f(pos_only=1, 2, kwd_only=3) ^ SyntaxError: positional argument follows keyword argument >>> f(1, 2, 3) Traceback (most recent call last): File "", line 1, in TypeError: f() takes 2 positional arguments but 3 were given

解包实参列表
简单来说,如果在调用函数时,我们的实参已经存放在了列表、元组或字典中,我们就可以通过*将元组、列表中的值按位置传参的方式传入函数,可以通过**将字典中的值按关键字传参的方式传入函数:
# 定义函数 >>> def add(x, y): ...return x + y ... # 直接传入报错 >>> args = (3, 4) ... print(add(args)) Traceback (most recent call last): File "", line 2, in TypeError: add() missing 1 required positional argument: 'y'# 解包tuple >>> args = (3, 4) ... print(add(*args)) 7# 解包list >>> args = [3, 4] ... print(add(*args)) 7# 解包dict >>> args = {"x": 1, "y": 2} ... print(add(**args)) 3# args元素个数与add所需参数个数不一致,报错 >>> args = [3, 4, 7] ... print(add(*args)) Traceback (most recent call last): File "", line 2, in TypeError: add() takes 2 positional arguments but 3 were given

Lambda 表达式
通常我们可以在需要匿名函数的地方使用lambda表达式,他不过是一种语法糖。例如对list的元素进行排序:
先调用help(list.sort)看看sort函数的定义:
>>> help(list.sort) Help on method_descriptor:# self 参数类似于其他类中的this;key和reverse必须使用关键字传参 sort(self, /, *, key=None, reverse=False) # 默认升序,返回None Sort the list in ascending order and return None. # 该排序方法会修改list且是稳定性排序 The sort is in-place (i.e. the list itself is modified) and stable (i.e. the order of two equal elements is maintained). # key要求传入一个方法,sort方法执行时,每个元素会被依次传入该方法并根据返回值进行排序 If a key function is given, apply it once to each list item and sort them, ascending or descending, according to their function values. # 是否切换为降序排序 The reverse flag can be set to sort in descending order.

弄清楚定义后,我们现在对[(7, 9), (5, 6), (3, 4), (6, 5)]分别按每个元组的第一个元素排序、第二个元素排序、两个元素之和进行排序:
使用非匿名函数:
>>> def f(x): ...return x[0]# 按每个元组的第一个元素排序 ... a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key=f) ... print(a) [(3, 4), (5, 6), (6, 5), (7, 9)]>>> def f(x): ...return x[0] ... a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key=f, reverse=True)#按每个元组的第一个元素 降序 排序 ... print(a) [(7, 9), (6, 5), (5, 6), (3, 4)]>>> def f(x): ...return x[1]# 按每个元组的第二个元素排序 ... a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key=f) ... print(a) [(3, 4), (6, 5), (5, 6), (7, 9)]>>> def f(x): ...return x[0] + x[1]# 按每个元组的两个元素之和排序 ... a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key=f) ... print(a) [(3, 4), (5, 6), (6, 5), (7, 9)]

使用lambda表达式就无需提前定义函数且写法非常简单:
>>> a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key= lambda x: x[0]) ... print(a) [(3, 4), (5, 6), (6, 5), (7, 9)]>>> a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key= lambda x: x[0], reverse=True) ... print(a) [(7, 9), (6, 5), (5, 6), (3, 4)]>>> a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key= lambda x: x[1]) ... print(a) [(3, 4), (6, 5), (5, 6), (7, 9)]>>> a = [(7, 9), (5, 6), (3, 4), (6, 5)] ... a.sort(key= lambda x: x[0] + x[1]) ... print(a) [(3, 4), (5, 6), (6, 5), (7, 9)]

函数注解
由于Python不需要指定变量类型,在给编码带来方便的同时会给他人调用函数带来一定的麻烦,因为这样不方便调用者弄清楚形参类型以及返回值类型。于是,函数注解的作用便显现出来:定义函数时可通过函数注解给形参以及函数返回值加上数据类型说明。例如,我们要定义一个计算两数之和的函数:
# 该函数未限制入参类型,既可以传入int型,也可以传入str型 >>> def add(x, y): ...return x + y ... print(add(1, 2)) ... print(add("1", "2")) 3 12# 加上函数注解,你以为就可以限制入参了吗? >>> def add(x: int, y: int) -> int: ...return x + y ... # 并没有什么用,只起到建议和提示的作用 >>> print(add("1", "2")) 12# 使用help函数查看 >>> help(add) Help on function add in module __main__:add(x: int, y: int) -> int# 打印函数的 __annotations__ 成员变量查看 >>> print(add.__annotations__) {'x': , 'y': , 'return': }# 如果你在给出参数类型说明的同时希望为其赋默认值,默认值可以是一个表达式,如:int = 1*10 >>> def add(x: int, y: int = 1) -> int: ...return x + y ... >>> print(add(1)) 2# help可以看到默认值 >>> help(add) Help on function add in module __main__:add(x: int, y: int = 1) -> int#打印__annotations__ 变量不显示默认值 >>> print(add.__annotations__) {'x': , 'y': , 'return': }

编码风格
最后,如果你想写出易于阅读、赏心悦目的代码,强烈推荐学习小插曲:编码风格。

    推荐阅读