0

Python 简明教程

 2 years ago
source link: https://geektutu.com/post/quick-python.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Python 简明教程

Python 中文教程

Python is a programming language that lets you work more quickly and integrate your systems more effectively. – python.org

可以直接从官网 python.org/downloads 下载安装,最新版本是 3.9.0。安装过程和普通的 Windows 软件一致。安装完成后,需将安装路径添加到环境变量中。

如果你使用的是 Ubuntu 等 Linux 发行版或 MacOS 系统,操作系统将自带 Python,无需安装。如果操作系统上只有 Python2,在 Debian 和 Ubuntu 上可通过如下方式安装 Python3:

sudo apt-get update && sudo apt-get install python3

在 MacOS 上,可通过如下方式安装

brew install python3

若安装成功,命令行运行 python -Vpython3 -V,你将看到:

$ python3 -V
Python 3.7.5

1 Hello World

Python 有两种运行方式:交互式和源文件。在命令行中键入 python3 并回车,则进入了 Python 解释器的交互模式:

$ python3
Python 3.7.5 (default, Nov 7 2019, 10:50:52)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

接下来,我们接着输入:print("hello world"),按下回车:

>>> print("hello world")
hello world
>>>

这样就完成了我们的第一个 Python 程序,向世界问好。print() 是 Python 中的一个内置函数,用来在标准输出中打印信息。

在交互模式下,每输入一行代码,按下回车,运行结果就会打印在屏幕上。因此,我们可以将 Python 当做一个简单的计算器,例如:

>>> 1 + 2 * 100
201
>>> 1 + 2 ** 3
9

** 是 Python 中的指数运算符,这里表示 2 的 3 次方。

我们在桌面新建文件夹 test,在 test 文件夹中新建文件 main.py,写入:

print("1 + 2 =", 1 + 2)

保存后,在命令行中切换到 test 文件夹,执行 python main.py,将会看到:

$ python3 main.py
1 + 2 = 3

一般我们使用 IDE,例如 VSCodePyCharm 写 Python 程序。IDE 集成了代码高亮、提示、命令行等功能,能够极大地提升代码的学习和开发效率。

2 基本语法

ok = True # 布尔值,表示真假,True 或 False
a = 2
b = 3.56
c = "hello world"
d = a * b # 求 a 和 b 的积
print("c =", c)
print('{0} + {1} = {2}'.format(a, b, d))

运行这段程序:

python3 main.py
c = hello world
2 + 3.56 = 7.12
  • 单行注释以符号 # 开头。
  • 变量一般由数字、字母、下划线构成,但只能以字母或下划线开头。
  • ok = True,表示将字面量,即布尔值 True 赋值给变量 ok。
  • a = 2,表示将字面量,即整数 2 赋值给变量 a
  • b = 3.56,表示将字面量,即浮点数 3.56 赋值给变量 b
  • c = "hello world",表示将字符串 hello world 赋值给变量 c。

单行字符串一般使用双引号 ",也可以使用单引号 ',多行字符串通常使用三个单引号或三个双引号。例如:

a = "I'm 极客兔兔"
b = 'c = "hello world"'
d = """这是一个多行字符串;
这是第二行;
这是第三行。
"""
print(a)
print(b)
print(d)

执行结果为:

$ python3 main.py                                                             
I'm 极客兔兔
c = "hello world"
这是一个多行字符串,
这是第二行
这是第三行

format 可以格式化字符串,{0} 表示用 format 的第一个参数的值替代,{1} 表示用第二个值替代,以此类推。

format 还有其他使用方式,省略序号或使用键值对:

name, age = "小明", 13 # 一行中可以声明多个变量
print("{}今年 {} 岁".format(name, age)) # 按顺序使用,{}可省略序号
print("{name}明年 {age} 岁".format(name=name, age=age+1)) # 使用键值对

执行结果:

$ python3 main.py
小明今年 13 岁
小明明年 14 岁

如果代码写的有问题,Python 执行时将会报错,例如这段:

age = 13
print("今年 { 岁\n明年 {} 岁".format(age, age+1)) # \n 表示换行打印
python3 main.py
Traceback (most recent call last):
File "main.py", line 2, in <module>
print("今年 { 岁\n明年 {} 岁".format(age, age+1))
ValueError: unexpected '{' in field name

报错时打印了发生错误的堆栈信息:

  • File "main.py", line 2 表示错误发生在 main.py 的第 2 行。
  • ValueError: unexpected '{' in field name 表示错误原因是 { 括号使用有误。在这里,我们没有成对使用大括号作为占位符,导致报错。

3 运算符与表达式

3.1 运算符

常见的加减乘除运算:

>>> 1 + 2 # 加
3
>>> 4 - 6 # 减
-2
>>> 2 * 3 # 乘
6
>>> 6 / 4 # 除
1.5
>>> 6 // 4 # 除取整
1
>>> 6 + 2 * (1 + 3) # 运算顺序和正常的数学运算一致,先乘除,后加减,括号优先
14
>>> 2 ** 4 # 指数,2 的 4 次方
16
>>> 6 % 4 # 取模/余数
2

大小比较运算:

>>> 12 < 18 # 小于
True
>>> 12 <= 18 # 小于等于
True
>>> 1.2 > 3.4 # 大于
False
>>> 1.2 >= 3.4 # 大于等于
False
>>> 12 == 18 # 等于
False
>>> 12 != 18 # 不等于
True

逻辑运算符:

>>> 12 >= 8 and 5 > 6 # and 布尔与/且,全真为真,有假为假
False
>>> 12 >= 8 or 5 > 6 # or 布尔或,全假为假,有真为真
True
>>> not 5 > 6 # not 布尔非
True

此外还支持 << (左移)、>> (右移)、& (按位与)、| (按位或)、^ (按位异或)、~ (按位取反) 等位运算符。

赋值运算符 =,将 = 右侧的值赋值给左侧,左侧需要一个变量。

a = 2
a = a * (3 + 4) # 将 a * (3 + 4) 的值 14 赋值给 a。

对一个变量进行计算,将计算的结果赋值给该变量,可以简写为:

a = 2
a *= 3 + 4

3.2 表达式

Python中,值、变量和运算符共同组成的整体称为表达式,通常我们所写出的程序语句包含若干个表达式。例如上述的 3 + 4 即构成了一个简单的表达式。值和变量也被称作为操作数,运算符也被称为操作符。

Python 语句是自上而下执行的,如果在这个过程中,我们希望通过一些条件判断,执行不同的逻辑怎么办呢?可以通过控制流语句实现,Python 中一共有 ifforwhile 三个控制流语句。

4.1 if 语句

if 用于检查条件是否为真,如果为真则执行,通常与 elseelif (else if) 结合使用。例如:

age = 13
if age >= 18:
print('adult')
if age < 18:
print('child')

Python 使用缩进代表不同的代码块,一般缩进使用 4 个空格表示。你可以看到 print() 前有 4 个空格。

上述代码可以简化成:

age = 13
if age >= 18:
print('adult')
else:
print('child')

如果我们有多个条件判断分支时,elif 就能派上用场了:

age = 45
if age < 18:
print('小孩')
elif age < 60:
print('中年人')
else:
print('老人')

4.2 for 语句

有些代码块需要执行多次,这种情况我们可以使用 for 循环语句,例如打印数字 1 - 5:

# main.py
for i in range(5): # 0, 1, 2, 3, 4 从0开始,不包含 5
print(i + 1, end=' ')

执行结果为:

python3 main.py
1 2 3 4 5
  • print() 函数默认以换行符结尾,如果我们希望替换成空格,只需要将参数 end 设置为空格即可。
  • range() 也是一个内置函数,用于生成数字序列,常用于 for 循环语句中。若只传入一个参数 N,代表生成 [0, N) 的整数序列,从 0 开始,不包含N。如果我们不想从 0 开始怎么办呢?range() 也支持传入多个参数,例如:
for i in range(2, 5): # [2, 5)
print("{0} * {0} = {1}".format(i, i ** 2))

# 结果如下
# 2 * 2 = 4
# 3 * 3 = 9
# 4 * 4 = 16

for i in range(1, 10, 2): # 第三个参数为步长 step,从1开始,每次加 2
print(i, end=' ')
# 1 3 5 7 9
for i in range(10, 2, -2): # 步长-2,从10开始,每次加-2
print(i, end=' ')
# 10 8 6 4

那如果在 for 循环中,遇到某个条件想退出循环呢?通常 for 还与 breakcontinue 结合使用:

for i in range(100):
if i <= 3:
continue # i <= 3,继续循环,不执行下面的语句
if i >= 10:
break # i >= 10 时,终止循环
print(i, end=' ')
# 4 5 6 7 8 9
  • continue 语句用来跳过当前循环语句块中的其余部分,然后继续执行循环的下一个迭代。
  • break 语句用来终止循环。
  • 因此,i <= 3 时,跳过了 print 语句,i >= 10 时循环被终止,那么这个程序将会打印出数字 4 5 6 7 8 9。

4.3 while 语句

for 循环语句类似,while 通常也用于循环表达式中。while 后面跟一个条件,条件为真时,执行 while 代码块;条件为假时,终止循环。一般 while 可以替代 for 循环,刚才我们使用 for 打印了数字 1 - 5,接下来用 while 改写:

i = 0
while i < 5: # 第1次迭代i==0,满足条件,第6次迭代时,i==5,不满足条件
i += 1
print(i, end=',')
# 1,2,3,4,5,

同样的,continuebreak 语句也能在 while 语句中使用。例如我们实现一个简单的功能,用户每输入一个数字,打印这个数字的平方,直到输入 0 时结束。

while True:
num = input('请输入一个数字: ')
num = int(num)
if num == 0:
print('结束')
break
print("{}^2 = {}".format(num, num ** 2))

运行结果:

python3 main.py
请输入一个数字: 12
12^2 = 144
请输入一个数字: 18
18^2 = 324
请输入一个数字: 0
结束
  • while True 代表进入无限循环,只有通过 break 语句才能结束。
  • input() 是一个内置函数,用于接受用户的输入,返回一个字符串。input 可以传入一个字符串,作为输入的提示语句。
  • num = int(num) 是将 num 转换为整型,因为字符串不能计算平方,只有数字才行。

在之前的例子中,我们已经使用了 printrangeinput 等 Python 常用的内置函数,对函数已经不陌生了。那函数是什么呢?函数可以理解为一块可以复用的代码块,可以为这个代码块起一个名字(函数名),也可以定义传入的参数(形参),以及返回结果(返回值)。函数定义一般长这个样子:关键字 def 作为函数的标识符,紧接着是函数名,函数名的命名规范与变量一致,通常由数字、字母和下划线构成,以字母或下划线开头。函数名后面有一对小括号和一个冒号 :,里面可以定义参数列表,也可以没有参数。接下来便是一个语句块作为函数体。

def 函数名(参数名1, 参数名2, ...):
body 函数体,函数体同样通过缩进来体现

那比如我们实现一个函数 area,用于计算长方形的面积,接受 2 个参数长 length 和宽 width,返回一个值,即面积。

def area(length, width):
return length * width

print('area1: ', area(10, 8)) # area1: 80,第一次调用
print('area2: ', area(6, 8)) # area2: 48,第二次调用
  • return 是 Python 中的一个关键字,用于函数体中,即跳出这个函数,不再执行。
  • return 可以跟一个或多个值作为返回值,也可以没有返回值。
  • 没有返回值时,return 事实上等价于 return NoneNone 是一个关键字,表示空。
  • 如果函数没有 return 语句,系统会自动在函数的结尾添加 return None 语句。
def calc(a, b):
return a // b, a % b # 多个返回值,事实上是一个元组(tuple)类型,后面章节会介绍

print(calc(8, 3)) # (2, 2)

x, y = calc(8, 5) # 两个返回值分别赋值给变量 x, y
print(x, y) # 1, 3
def log(mode):
if mode == 'debug':
print('debug mode')
return
print('release mode')

print(log('release'))
# release mode
# None

5.1 局部变量与全局变量

x = 10 # x 是全局变量,可在其他函数中使用

def print_global():
print(x)

def print_local():
x = 100 # x 是局部变量,不影响全局变量中的 x
print(x)

print_global() # 10
print_local() # 100
print_global() # 10

我们定义了全局变量 x,在 print_local 中定义了与全局变量同名的局部变量 x,并将 100 赋值给 x,在这里修改的是局部变量的值,局部变量只在这个函数内部有效,全局变量不会受到影响。因此,两次 print_global 的结果都是 10。

定义局部变量的好处在于,控制变量的作用域范围,减少各个函数之间的干扰。全局变量可以在多个函数之间共享,一般建议仅将只读的变量设置为全局变量,比如圆周率 π 的值。那如果我们想修改全局变量怎么办呢?可以使用 global 关键字:

x = 10 # x 是全局变量,可在其他函数中使用

def print_global():
print(x)

def print_local():
global x
x = 100
print(x)

print_global() # 10
print_local() # 100
print_global() # 100

print_local 中,使用 global x 告诉 Python x 是一个全局变量,而非局部变量。因此 x = 100 将修改全局变量 x 的值。所以第二个 print_global() 将打印 100。

5.2 可选参数与默认值

如果有多个参数,那能不能给某些参数设置默认值,这样用户可以选择性地传入或不传入该参数的值呢?答案是可以的,Python 允许给参数设置默认值,在使用者看来,就像是实现了C++的函数重载一样。

重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,也就是说用同一个函数完成不同的功能,这就是重载函数。

def greet(msg, times=1):
for i in range(times):
print(msg)

greet('Hi, Jack')
greet('Hello, Mr Dai', 3)

执行结果:

Hi, Jack
Hi, Tom
Hi, Tom
Hi, Tom
  • greet 函数的功能是打印 msg,默认是1次,但可以通过参数 times 控制。
  • 带默认值的参数只能位于参数列表的末尾,不能位于非默认值参数前面。例如 def greet(times=1, msg) 是不允许的。
  • 默认值参数可以有 0 或多个。

Python 中,参数的传递还可以更加地灵活,除了按顺序传入以外,还可以通过键值对的方式传入:

greet('Hi, Jack', times=2) # 混合方式
greet(msg='Hi, Tom', times=3)
greet(times=3, msg='Hi, Tom') # 均为键值对的情况下,顺序没有关系

键值对传参特别适用于参数列表非常多,且大部分均有默认参数的情况。无需关注参数的顺序,而只需将需要设置的几个参数传入即可。

5.3 可变参数

Python 还支持可变参数。函数定义时如果参数的个数是不确定的,那么就适合使用可变参数来代替。可变参数有 2 种,元组式(tuple,元组可以认为是一个不可变的有序集合)和字典式(dict)。元组式可变参数用 *参数名 表示,传入的实际参数会构成一个元组,例如:

# 实现一个求和函数,支持传入任意个数字
def sum_n(*nums):
s = 0
for num in nums:
s += num
return s

print(sum_n(1, 2, 3)) # 6
print(sum_n(1, 2, 3, 4, 5)) # 15

字典式(键值对式)可变参数用**参数名表示,传入的实际参数会构成一个字典(dict),例如:

def print_student(**students):
for name, age in students.items():
print('{}今年 {} 岁'.format(name, age))

print_student(小明=8, 小红=7)

执行结果:

小明今年 8 岁
小红今年 7 岁

元组(tuple) 和 字典(dict) 都是 Python 内置的数据结构,我们在下一章节会讲到。

5.4 文档字符串 __doc__

给每一个函数写文档是编程的好习惯,在 Python 中,对每个函数来说,有一个内置的属性 __doc__ 保存了函数的说明文档,Python 中称之为 DocStrings。那怎么定义这个属性呢?

def print_student(**students):
'''Prints name and age for every student.

key is name, and value is age.'''
for name, age in students.items():
print('{}今年 {} 岁'.format(name, age))

print_student(小明=8, 小红=7)
print(print_student.__doc__) # 打印 __doc__ 的值
help(print_student)
  • 函数体一开始使用三个单引号'''标志 DocStrings 的开始。
  • 第一行描述函数的作用,首字母大写。第二行为空行,第三行是详细的描述,可以包括函数每一个参数的介绍等。

执行结果:

小明今年 8 岁
小红今年 7 岁
Prints name and age for every student.

key is name, and value is age.
Help on function print_student in module __main__:

print_student(**students)
Prints name and age for every student.

key is name, and value is age.
  • help() 函数也是 Python 的内置函数,提供一种更优美的方式查看某个函数的 DocStrings,通常在交互模式下使用。

6 数据结构

Python 内置了常用的几种数据结构:列表(list)、元组(tuple)、字典(dict) 和集合(set),几乎所有程序都会用到这几种数据结构。

6.1 字符串(string)

字符串可以说是最常用的数据类型了。字符串可以使用 "'"""''' 表示。三引号通常用于表示多行字符串。字符串是字符序列,在 Python 中,序列支持下标索引、for 循环、切片等系列操作,后面提到的列表(list)、元组(tuple)也都属于序列。字符串是不可变数据类型,不支持修改。

s = "I'm geektutu"
print(len(s)) # 12
print(s[0], s[-1]) # I u

[] 操作符可以按照下标索引到元素的值,下标从 0 开始,支持负数,-1 表示最后一个元素,以此类推。[] 除了可以用于下标索引外,还可以用来切片,例如:

s = "I'm geektutu"
print(s[:-3]) # I'm geekt,等价于 s[0:-3]
print(s[4:]) # geektutu,等价于 print(s[4:len(s)])
print(s[4::2]) # gett
print(s[::-1]) # tutkeeg m'I

切片能够快速截取序列中的一部分,使用方式可以表示为 [start:end:step],与 range 类似,包含开始,不包含结束。

  • start 默认值为 0,如果为 0,可以省略不写。
  • end 默认值为列表的长度,如果为列表长度,可以省略不写。
  • step 默认值为 1,如果为1,可以省略不写。

6.2 列表(list)

list 是一种表示有序项集合的数据结构。有序且允许重复,支持增删查改,是一种可变数据类型。

persons = list() # 声明一个空列表
persons = [] # 声明一个空列表
persons = ['Tom', 'Jack', 'Jack', 'Sam'] # 声明一个非空列表
persons.append('KangKang') # 添加一个元素
print(persons[1]) # Jack 打印第1个元素,下标从 0 开始。
persons.remove('Jack') # 删除 Jack,只删除第一次出现的位置
print(persons) # ['Tom', 'Jack', 'Sam', 'KangKang']
del persons[-2] # 删除倒数第二个元组 Sam
print(persons) # ['Tom', 'Jack', 'KangKang']

persons.sort() # 排序
# 遍历列表
for name in persons:
print(name, end=' ') # Jack KangKang Tom
  • append 添加元素,remove 按值删除某个元素,del 按下标删除元素。
  • sort() 用于给列表排序。

列表和字符串一样,是一种序列,因此也支持切片。

numbers = [2, 4, 6, 8, 10]
print(len(numbers)) # 长度 5
print(numbers[:3]) # 2 4 6
print(numbers[0:3]) # 2 4 6
print(numbers[1:-1]) # 4 6 8
print(numbers[1:]) # 4 6 8 10
print(numbers[1:5:2]) # 4 8
print(numbers[1::2]) # 4 8

列表的值可以是任意类型,而且一个列表中允许不同类型的值存在,当然也允许嵌套列表存在。

a = [1, 1.3, "Student", [1, 2, 3]]

判断一个值是否在一个列表中,可以使用 in

print("Student" in a) # True

列表与字符串有一个非常常用的处理组合 splitjoin

s = "1,5,2,4,3"
parts = s.split(',')
print(parts) # ['1', '5', '2', '4', '3']
parts.sort()
print(':'.join(parts)) # 1:2:3:4:5
  • split 用于将字符串按照某个分隔符切割成列表。
  • join 用于将字符串列表按照某个分隔符合并在一起。

关于列表的更多操作参考 list - Python官方文档

6.2 元组(tuple)

元组也是有序集合,用小括号表示。很多特性与列表(list) 一致,不同点在于元组是不可变数据类型,即不允许增删改。

students = ('Tom', 18, 'Jack', 20)
print(len(students)) # 4
print(students[1:3]) # 切片:(18, 'Jack')

如果尝试修改元组,会出现如下报错:

students[0] = 'KangKang'
Traceback (most recent call last):
File "main.py", line 4, in <module>
students[0] = 'KangKang'
TypeError: 'tuple' object does not support item assignment

关于元组的更多操作参考 tuple - Python官方文档

6.4 字典(dict)

字典由若干个键值对构成,能够快速地根据键(key)查找到对应的值(value),在一个字典中,键是不能重复的。字典是可变数据类型,支持增删查改。

students = {} # 声明空字典
students = dict() # 声明空字典
students = {
'Tom': 18,
'Jack': 20,
'Same': 19
}

students['KangKang'] = 17 # 新增
students['Tom'] = 20 # 修改
print(students['Tom']) # 20, 通过 key 索引
del students['Jack'] # 删除

# 遍历
for name, age in students.items():
print(name, age)
# Tom 20
# Same 19
# KangKang 17
  • items() 可以同时获取键和值,除此之外,字典还支持仅获取所有键 keys(),所有值values()等方法,字典是无序的,这三个方法返回值的顺序是不能保证的。

  • 判断字典中是否包含某个键,同样可以使用 in,例如 if 'Tom' in students

  • 获取字典的键值对个数,可以使用 len,例如len(students)

关于字典的更多操作参考 dict - Python官方文档

6.4 集合(set)

Python 中的集合与数学中的集合类似,特点是无序且不重复。

s = set() # 定义空集合
s1 = set([1, 2, 2, 2, 3]) # 非空集合
s1.add(2) # 添加元素,重复则不添加
s1.add(10)
s1.remove(3) # 删除
print(s1) # {10, 1, 2}

s2 = set([5, 6, 10])

print(s1 | s2) # 并集 {1, 2, 5, 6, 10}
print(s1 & s2) # 交集 {10}
print(s1 - s2) # 差集 {1, 2}
  • set([1, 2, 2, 2, 3]) 将列表转换为集合,自动去重,同样也可以使用 list() 将集合转换为列表。

关于集合的更多操作参考 数据结构 set - Python官方文档

7 输入输出

之前的例子我们使用了标准输入输出函数 inputoutput 实现了简单的功能。Python 常用于数据挖掘分析,文本处理是最基本的能力,使用 Python 进行文件读写也非常简单。

下面是一个非常简单的例子:将字符串 s 写入文件 1.txt

s = '''第一行
第二行
第三行'''

f = open('1.txt', 'w')
f.write(s)
f.close()
  • open 是 Python 用于读取文件的内置函数,第一个参数是文件路径,第二个参数是打开模式,w 代表写模式, r代表只读模式。w 模式打开文件后,文件会被清空,如果需要追加写,则需要以w+模式打开文件。
  • 如果打开文件成功,open会返回一个文件句柄,我们可以使用这个句柄对文件进行操作。
  • 操作完毕后,需要将文件关闭。

Python 还提供了另一种更安全、简单的方式 with as

s = '''第一行
第二行
第三行'''

with open('1.txt', 'w') as f:
f.write(s)

with 语句会在 with 内部的代码块执行完毕后,执行资源回收的操作,对于文件来说即关闭文件。

读取文件,并统计字数:

with open('1.txt', 'r') as f:
s = f.read()
print(len(s)) # 11

我们也可以使用 readlines() 读取文件的所有行:

with open('1.txt', 'r') as f:
s = f.readlines()
for line in s:
print(line, end='')

还有一种更高效的方式,直接遍历文件句柄 f

with open('1.txt', 'r') as f:
for line in f:
print(line, end='')

尽管我们想要将代码写得尽善尽美,但是出现异常还是难免的。如果我们不对异常做任何的处理,程序会立即退出。Python 提供了 try except finally 机制,给开发者提供了一个处理异常的机会。

# main.py
with open('2.txt', 'r') as f:
print(f.read())
print('done')

如果我们执行上述程序,会出现如下错误:

Traceback (most recent call last):
File "main.py", line 2, in <module>
with open('2.txt', 'r') as f:
FileNotFoundError: [Errno 2] No such file or directory: '2.txt'

程序在第二行就退出了,错误原因是 2.txt 不存在。那我们如何捕获到这个错误并处理呢?

# main.py
try:
with open('2.txt', 'r') as f:
print(f.read())
except Exception as e:
print(e)
finally:
print('done')

程序正常执行结束:

[Errno 2] No such file or directory: '2.txt'
done
  • try 语句块中包含可能发生异常的代码,如果发生异常,将跳转到 except 语句块执行。
  • 无论是否发生异常,finally 中的代码都会得到执行,finally 是可选的。

我们也可以在 except 中处理完毕之后,继续将异常抛出,留给调用方处理。

# main.py
try:
with open('2.txt', 'r') as f:
print(f.read())
except Exception as e:
print(e)
raise e
finally:
print('done')

执行结果:

[Errno 2] No such file or directory: '2.txt'
done
Traceback (most recent call last):
File "main.py", line 7, in <module>
raise e
File "main.py", line 3, in <module>
with open('2.txt', 'r') as f:
FileNotFoundError: [Errno 2] No such file or directory: '2.txt'

9.1 使用标准库模块

Python 标准库内置了大量的模块,提供了非常丰富的功能。比如数学库 math

import math

print(math.__name__) # 模块名 math
print(math.ceil(4.3)) # 向上取整 5
print(math.floor(4.8)) # 向下取整 4
  • 使用 import 导入标准库 math,并调用了 mathceilfloor 函数。
  • 每一个模块有一个内置属性 __name__,表示模块的名称,如果开发者正在独立运行该模块,则模块名称将是 __main__

例如,执行 main.py

# main.py
print(__name__)
if __name__ == '__main__':
print('正在独立运行该模块')

将会输出:

__main__
正在独立运行该模块

如果我们导入的模块名有冲突,可以使用 as 为导入的模块起一个别名

import math as math2

print(math2.__name__) # 模块名 math
print(math2.ceil(4.3)) # 向上取整 5
print(math2.floor(4.8)) # 向下取整 4

有时候,导入的模块名路径很深,可以使用 from xxx import xxx来简化导入的路径:

import os
print(os.path.join('/tmp', 'a', 'b'))
# 可以替换为
from os import path
print(path.join('/tmp', 'a', 'b'))

9.2 使用自己实现的模块

新建一个文件 calc.py,在里面实现如下的函数:

def area(length, width):
return length * width

print('this is calc module')

if __name__ == '__main__':
assert(area(3, 4) == 12)
print('test done')

执行 python calc.py,将会输出:

this is calc module
test done

main.py 中我们可以导入模块 calc 并使用,在 Python 中一个 .py 文件就可以被视为一个模块:

import calc

if __name__ == '__main__':
print(calc.__name__)
print(calc.area(5, 10))

执行 python main.py,将会输出:

this is calc module
calc
50

当模块被导入时会执行该模块的代码,因此也打印了 this is calc module,但没有打印 test done

calc.py 作为一个模块导入时,属性 __name__ 与文件名相同,即等于 calc,因此没有进入到 if 分支中,而被独立执行时,__name__ 的值是 __main__,因此进入到了 if 分支,打印了 test done

因此,我们可以利用这个特性,在模块被独立执行时运行一些代码,比如简单的测试逻辑,但不影响被导入时的执行逻辑。

9.3 使用第三方模块

Python 拥有非常丰富的第三方模块,比如著名的爬虫框架 scrapy,数学基础库 numpy、数据处理利器 pandas 等。如果我们想使用第三方模块,只需要使用 pip 命令安装即可。

例如安装 numpy:

pip3 install numpy

如果你的机器上同时安装了 Python2 和 Python3,给特定的 Python 版本安装可以使用:

python3 -m pip install numpy

如果国内下载网速过慢,可以通过 -i 选项指定下载源:

pip3 install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成后,就可以像使用标准库一样使用 numpy 了:

import numpy as np

a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.array([[1, 5, 8], [2, 5, 6]])
print (a - b) # 两个矩阵相减
# [[ 0 -3 -5]
# [ 2 0 0]]

10 面向对象编程

Python 是一门既支持过程式编程,又支持面向对象编程的一门语言。

面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。

面向对象编程的三大特性:

  • 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式。
  • 继承: 子类从父类继承方法,使得子类具有父类相同的行为。
  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

10.1 类与对象

Python 中使用关键字 class 声明一个类,一般继承基类 object

class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age

def hello(self):
print('你好,我是{},今年{}岁'.format(self.name, self.age))

if __name__ == '__main__':
jack = Student('Jack', 18)
jack.hello()
  • 类内部声明的方法默认是实例方法,第一个参数是 self,代表实例本身,调用时省略。
  • __init__ 是一个类的构造方法,第一个参数是 self,后面的参数根据需要声明,使用时使用 类名(参数) 可创建出一个属于该类的一个实例对象。nameage 都属于实例变量,实例变量属于该实例,不与其他实例共享。
  • 其他方法的声明与普通的函数实现类似,唯一不同点在于实例方法可以通过参数 self 获取实例的属性或调用实例的其他方法。

10.2 类方法与类变量

实例方法和实例变量是针对对象实例而言的,与实例方法和实例变量相对应的是类方法和类变量,属于该类的所有实例都可以共享,可以通过类.方法实例.方法 的方式使用。

class Student(object):
school = '东方小学' # 类变量

def __init__(self, name, age):
self.name = name # 实例变量
self.age = age # 实例变量

def hello(self):
print('你好,我是{},今年{}岁'.format(self.name, self.age))

@classmethod
def print_school(cls): # 类方法
print(cls.school)

if __name__ == '__main__':
jack = Student('Jack', 18)
tom = Student('Tom', 20)
jack.print_school()
tom.print_school()

Student.school = '东明小学' # 修改类变量
jack.print_school() # 东明小学
tom.print_school() # 东明小学
  • 实例变量在构造函数 __init__ 内部声明,类变量在外部声明。
  • 类内部声明的方法默认为实例方法,使用 @classmethod 声明类方法,第一个参数cls 代表类自己。

10.3 静态方法

还有一类方法,既不会访问实例变量和方法,也不会访问类变量和方法,仅仅是一个辅助函数,比如因为某个实例方法实现过长,想把其中的一部分代码抽取出来独立成一个方法,提高代码可读性。而这个辅助函数仅对这个类有用,对其他类没有用。这种情况下,我们通常会将其声明为静态方法。

class Student(object):
def __init__(self, name, age):
self.name = name # 实例变量
self.age = age # 实例变量

def hello(self):
print('你好,我是{},今年{}岁'.format(self.name, self.age))

@staticmethod
def help_func():
print('我是一个静态方法')

if __name__ == '__main__':
jack = Student('Jack', 18)
Student.help_func()
jack.help_func()
  • 静态方法使用 @staticmethod 声明,与普通的全局函数没有任何区别,可以通过类.方法实例.方法 的方式使用。与实例方法和类型方法相比,没有 selfcls 参数。

10.4 继承

class Rectangle(object):
def __init__(self, length, width):
self.length = length
self.width = width

def area(self):
return self.length * self.width

class Square(Rectangle):
def __init__(self, length):
super(Square, self).__init__(length, length)

if __name__ == '__main__':
s = Square(4)
print(s.area())
  • Square 继承了 Rectangle,因此拥有了 Rectangle 的所有属性和方法。
  • Square 可以根据需要覆盖父类的方法,在这里 Square 覆盖了父类的构造函数,参数列表从原来的 2 个变为了 1个。
  • 子类可以通过 super(子类名, self).方法 的方式调用父类的方法。

11 单元测试

为每一个模块编写单元测试是非常好的习惯,Python 也内置了一个单元测试库 unittest

新建一个文件 calc.py,实现 area 和 volume 两个函数:

def area(length, width):
if length < 0 or width < 0:
return 0
return length * width

def volume(length, width, height):
if length < 0 or width < 0 or height < 0:
return 0
return length * width * height

新建文件 calc_test.py 添加测试用例:

import unittest
import calc

class TestCalc(unittest.TestCase):
def test_area(self):
self.assertEqual(calc.area(10, -1), 0)
self.assertEqual(calc.area(10, 8), 80)

def test_volume(self):
self.assertEqual(calc.volume(2, -1, 4), 0)
self.assertEqual(calc.volume(2, 3, 4), 24)

if __name__ == '__main__':
unittest.main()
  • 添加测试用例的过程非常简单,定义一个类,继承 unittest.TestCase,然后定义一个或多个 test_ 开头的方法即可。每一个 test_ 开头的方法视为一个用例,这是 unittest 测试框架的约定。
  • 测试用例中,可以使用 assertEqualassertTrue 等方式检查预期输出。
  • unittest.main() 将加载该模块中定义的所有用例并执行。
$ python3 calc_test.py 
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

使用如下方式可以指定测试某个模块、某个测试类,甚至是只运行某个测试用例:

python -m unittest test_module1 test_module2
python -m unittest test_module.TestClass
python -m unittest test_module.TestClass.test_method

python3 calc_test.py 等价于 python3 -m unittest calc_test。如果只想执行 test_volume 方法,可以这么调用:

python3 -m unittest calc_test.TestCalc.test_volume

11.1 setUp 与 tearDown

有时候,每个用例执行前后需要一些相同的准备动作和收尾动作,比如打开文件和关闭文件。如果每个用例里都调用一次,就会异常繁琐。与其他测试框架类似,unittest 提供了 setUptearDown 功能,用于设置每个用例执行前后的一些指令。unittest 还提供了 setUpClasstearDownClass 两个方法,用于设置某个测试类所有用例执行前后的一些指令。

import unittest

class TestCalc(unittest.TestCase):
@classmethod
def setUpClass(cls):
print('所有用例执行前')

@classmethod
def tearDownClass(cls):
print('所有用例执行后')

def setUp(self):
print('单个用例执行前')

def tearDown(self):
print('单个用例执行后')

def test_1(self):
pass

def test_2(self):
pass

if __name__ == '__main__':
unittest.main()

执行结果如下:

$ python3 -m unittest calc_test
所有用例执行前
单个用例执行前
单个用例执行后
.单个用例执行前
单个用例执行后
.所有用例执行后

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

关于单元测试的更多用法,可以参考 unittest - Python 官方文档

这篇文章托管在 github,如果有错别字或其他修改建议,可以直接提 PR,感谢您的阅读和贡献。


专题: Python 简明教程

本文发表于 2020-10-17,最后修改于 2022-04-21。

本站永久域名「 geektutu.com 」,也可搜索「 极客兔兔 」找到我。


上一篇 « Go 语言笔试面试题(代码输出) 下一篇 » Go 接口型函数的使用场景


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK