惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 布谷鸟过滤器 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading 布谷鸟过滤器 Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式
Python 快速入门
2020-01-24 · via 蛮荆

2020-01-24 Python Python Quick Start


前言

人生苦短,我用 Python。

Python 语言的最大特点就是足够简洁,有任意编程语言的开发者,都可以看着手册直接上手快速开发,尤其是对于已经有 Golang 开发经验的开发者,感觉会更加熟悉。

本文所有代码的 Python 运行环境:

$ python --version

Python 3.12.3

‼️注意: 如果读者是较低版本,部分代码输出示例可能和文中不同。

本文内容参照 Python 从入门到实践 目录顺序,时间充裕的读者可以看看原书,写的不错。

安装

Ubuntu, Windows/WSL2, MacOS, 开发者使用的 3 个主流操作系统自带 Python, 无需安装。

如果操作系统上只有 Python2, 可以使用下面的命令安装 Python3。

Ubuntu 和 Windows/WSL2 (针对 Ubuntu 系统) 使用下面的命令安装:

$ sudo apt update && sudo apt install python3

MacOS 使用下面的命令安装:

$ brew install python3

安装完成后,查看版本号:

$ python3 --version

如果希望简化命令,可以利用软连接 (ln 命令) 将 python3 重命名一份为 python:

# 通过软连接复制 & 重命名 1 个 python 可执行文件
$ sudo ln -s $(which python3) /usr/local/bin/python

# 复制完成后,直接使用 python (执行 python3) 命令即可
$ python --version

# 输出如下
Python 3.12.3

Python 文件执行方法

$ python 文件名称

# 示例 1
python main.py

# 示例 2
python test.py

# 示例 3
python debug.py

Hello World

将下面的代码写入 main.py 文件 (注意顶部的注释部分是必须的):



print("hello world")

使用 Python 命令,执行 Python 文件

$ python main.py

关键字

Python 和其他主流编程语言一样,关键字不能作为变量、函数、方法、任意数据的名称。

Python 中的关键字有:



import keyword

# 高逼格输出 Python 语言关键字
print(keyword.kwlist)

输出如下:

['False', 'None', 'True', 'and', 'as', 
'assert', 'async', 'await', 'break', 'class', 
'continue', 'def', 'del', 'elif', 'else', 
'except', 'finally', 'for', 'from', 'global', 
'if', 'import', 'in', 'is', 'lambda', 
'nonlocal', 'not', 'or', 'pass', 'raise', 
'return', 'try', 'while', 'with', 'yield']

分类

  1. 控制流:

    • if, else, elif:条件控制
    • for, while, break, continue: 循环控制
    • try, except, finally, raise: 异常处理
  2. 函数/类:

    • class:定义类
    • def:定义函数
    • lambda:定义匿名函数
    • return:从函数返回值
    • yield:在生成器中返回值
  3. 逻辑运算符号:

    • and, or, not, is, in
  4. 变量作用域:

    • global:声明全局变量
    • nonlocal:声明使用外部(非全局)变量
  5. 其他关键字:

    • import, from:导入模块
    • as:用于导入时的别名
    • with:上下文管理器
    • pass:什么也不做
    • assert:用于调试的断言
    • del:删除对象
    • async, await:异步编程关键字

基本语法格式

  1. 任意表达式和语句之后不需要加 ;


print('hello world')

print('hello world')

print('hello world')
  1. 条件表达式缩进,函数体缩进不需要加 { }, 直接使用 tab 制表符缩进即可 (Python 游标卡尺的梗由来),所以要特别注意缩进 (很容易引发 Bug )


while True:
    print('hello world')
    if 200 > 100:    
        print('hello world')
        if 1 > 0:    
            print('hello world')
    break
  1. 单行注释使用 # 开头


print('hello world') # output hello world
  1. 多行注释 (文档) 使用一对 """ 包起来


print('hello world') # output hello world

while True:
    """
    something ...
    will output `hello world` three times
    something ...
    """
    print('hello world')
    if 200 > 100:    
        print('hello world')
        if 1 > 0:    
            print('hello world')
    break

变量

Python 作为弱类型语言,变量不区分数据类型,并且变量在声明定义之后,可以随时修改其值的数据类型。



# 此时变量 msg 是字符串类型
msg = "Hello World"
print(msg)

# 此时变量 msg 是整型
msg = 100
print(msg)

# 此时变量 msg 是浮点型
msg = 3.14
print(msg)

在上面的代码中可以看到,Python 中变量的数据类型和值可以随时修改。

多个变量赋值时,如果希望代码保持在同一行,使用 ; 分割即可



five = 5; three = 3
print(five, three)

数据类型

限于篇幅,本文只讲解一些常用的数据类型。

特殊值

  • None: 表示一个空值或未定义的值

布尔值

Python 中的布尔值首字母是大写,这一点和主流编程语言不同。

  • True
  • False

整型/浮点型



print(100 + 200)
print(100 - 200)
print(100 * 200)
print(100 / 200)

print(100 ** 3) # ** 表示幂运算符

浮点数和整数略有差异的地方就是: 浮点数存在 精度问题,所以小数的位数可能是不确定的。



print(0.1 + 0.2)
print(0.1 - 0.2)
print(0.1 * 0.2)
print(0.1 / 0.2)

print(0.1 ** 3) # ** 表示幂运算符

执行输出如下:

0.30000000000000004
-0.1
0.020000000000000004
0.5
0.0010000000000000002

通过输出结果可以看到,当小数位数越多越多时,精度就可能会丢失。

类型转换

弱类型语言中的常用功能之一就是:将数字转换为字符串 (或者将字符串转换为数字),然后再进行相应的运算。

通过 str 函数进行强制转换即可。



# 整型转换字符串
print("2 ^ 10 = " + str(1024)) # 2 ^ 10 = 1024

# 浮点型转换字符串
print("π = " + str(3.14)) # π = 3.14

# 字符串转换整型
print("2 ^ 10 = ", int('1024')) # 2 ^ 10 =  1024

# 字符串转换符点型
print("2 ^ 10 = ", float('1024')) # 2 ^ 10 = 1024.0


# 整型转换布尔类型
# 整型不等于 0, True
# 整型等于 0, False
print(bool(1024), bool(0)) # True False

# 字符串转换布尔类型
# 字符串长度不等于 0, True
# 字符串长度等于 0, False
print(bool('1024'), bool('')) # True False

字节

字节(bytes)用于存储二进制数据。

bs = b'Hello, World!'

# 输出 bytes 对应的 ASCII 数字
# 也就是该字节的整数表示
for b in bs:
    print(b)

不可变性

字节一旦创建,就不能修改其中的元素。

bs = b'Hello, World!'

bs[0] = 'h' # TypeError: 'bytes' object does not support item assignment

字符串

Python 中的字符串使用双引号或者单引号,直接包起来即可。



print("Hello World")

print('Hello World')

单引号和双引号可以互相包含,灵活运用:



print('Hello"" World')

print("Hello'' World")

使用 \ 作为转义符号:



print('Hello World\'')

print("Hello\\ World")

不可变性

字符串一旦创建,就不能修改其中的元素。

msg = 'Hello world'

msg[0] = 'h' # TypeError: 'str' object does not support item assignment

子字符串

直接使用 in 表达式检测字符串中是否包含指定子字符串。

msg = 'hello world'
print('o' in msg) # True

拼接

直接使用 + 即可。



print("hello" + " " + "world")

大小写转换

  • title() 将每个单词的首字母大写
  • upper() 将字符串中的字母全部转换为大写
  • lower() 将字符串中的字母全部转换为小写


msg = "hello world"
print(msg.title()) # Hello World
print(msg.upper()) # HELLO WORLD
print(msg.lower()) # hello world

删除空白字符



msg = " hello world"
print(msg.lstrip())
print(msg.rstrip())
print(msg.strip())

条件表达式

if 语句不需要单独使用 () 括起来,表达式语句末尾跟随一个 : 分号即可。

比较运算符分为 >, >=, <, <=, ==, !=



five = 5; three = 3
if five > three:
    print(five, " > ", three)

大小写敏感

Python 中比较字符串时大小写敏感,例如,两个大小写不同的值会被视为不相等:



print('hello' == 'Hello') # False

如果希望字符串比较时不区分大小写,可以先全部转换为大写或者小写,然后再比较。

布尔运算符

Python 中的布尔运算符并不是 &&||,而是使用单词 andor这一点和主流编程语言不同。

使用 and 表示条件与操作:



five = 5; three = 3; one = 1

if five > three and five > one:
    print(five, " > ", three, " && ", five, " > ", one) # 5  >  3  &&  5  >  1

使用 or 表示条件或操作:



hour = 23
if hour >= 22 or hour < 6:
    print("Sleeping")

包含运算符

使用关键字 in 表示包含运算关系符,判断指定的值是否存在于列表中。



my_list = [1, 2, 3, 4, 5]
if 100 in my_list:
    print("100 in list")
else:
    print("100 not in list")

当然,对应的 not in 表示非包含运算关系符。



my_list = [1, 2, 3, 4, 5]
if 100 not in my_list:
    print("100 not in list")
else:
    print("100 in list")

多行语句

如果 if 条件表示式对应的逻辑包含多行代码语句,保持对应的缩进即可。



my_list = [1, 2, 3, 4, 5]
if 1 in my_list:
    print("100 in list")
    print("100 in list again")
    print("100 in list again")
    print("100 in list again")
else:
    print("100 not in list")

多个条件语句

当判断条件超过 2 个时,使用 if - else 就无法覆盖到所有条件,这时可以使用 if-elif-else 结构来描述多个逻辑。



score = 80
if score > 96:
    print('A+')
elif score > 90:
    print('A+')
elif score > 85:
    print('B+')
elif score > 80:
    print('B')
elif score > 75:
    print('C+')
elif score > 70:
    print('C')
else:
    print('D')

循环表达式

while 循环:



n = 1
while n <= 5:
    print(n)
    n+=1

转换为等价的 for 循环:



for i in range(1, 6):
    print(i)

breakcontinue 和其他语言中的语义一致,这里不再赘述。

配合 else 语句

Python 中提供了一个有趣的特性: 循环语句 + else 语句。



numbers = [1, 2, 3, 4, 5]
for num in numbers:
    if num == 10:
        print("Found 10!")
        break
else:
    print("10 not found.")

上面的示例代码中,如果 for 循环没有被 break 语句打断,else 子句就会执行。

下面是等价的 while 循环实现代码:



n = 1
while n <= 5:
    if n == 10:
        print("Found 10!")
        break
    else:
        n+=1
else:
    print("10 not found.")

列表

Python 中并没有内置的 数组 数据结构,而是使用 list (列表) 作为常用的数据结构之一,列表支持动态大小的数组,可以包含不同类型的元素。

使用 [] 定义即可,每个元素使用 , 分隔开。



# 整型 list
my_list = [1, 2, 3, 4, 5]
print(my_list)

# 浮点型 list
my_list = [1.1, 2.2, 3.3, 4.4, 5.5]
print(my_list)

# 字符串 list
my_list = ['hello', 'world', 'Python']
print(my_list)

# 拥有不同数据类型的 list
my_list = [100, 3.14, 'hello', 'world']
print(my_list)

获取长度



my_list = [1, 2, 3, 4, 5]

print(len(my_list))

判断是否为空

Python 在列表为空时返回 False,在列表不为空时返回 True。



my_list = []
if my_list: 
    print("list is not empty")
else:
    print("list is empty")

索引访问



my_list = [1, 2, 3, 4, 5]
# 访问第 1 个元素
print(my_list[0])

# 访问最后 1 个元素
print(my_list[-1])

# print(my_list[5]) # 越界错误: IndexError: list index out of range

区间访问

使用两个索引作为区间索引,可以获取位于这两个索引之间的所有元素。



my_list = [1, 2, 3, 4, 5]

# 获取第 2 个元素到第 5 个元素之间的所有元素
print(my_list[1:4]) # [2, 3, 4]

修改元素



my_list = [1, 2, 3, 4, 5]
print(my_list)

my_list[0] = 100
my_list[1] = 200
print(my_list) # [100, 200, 3, 4, 5]

添加元素

1. 在列表末尾追加元素



my_list = [1, 2, 3, 4, 5]
print(my_list)

my_list.append(6)
my_list.append(7)
print(my_list) # [1, 2, 3, 4, 5, 6, 7]

2. 在列表指定位置追加元素



my_list = [1, 2, 3, 4, 5]
print(my_list)

# 在列表第 1 个位置之前插入 0
my_list.insert(0, 0)
# 在列表第 2 个位置之前插入 1.5
my_list.insert(2, 1.5)

print(my_list) # [0, 1, 1.5, 2, 3, 4, 5]

删除元素

1. 删除指定位置的元素

根据列表索引删除:



my_list = [1, 2, 3, 4, 5]
print(my_list)

# 删除第 1 个元素
# 下面两种语法等价
del my_list[0]
#del(my_list[0])

# 此时列表还剩下 4 个元素
# 删除第 1  个元素
del my_list[0] # [3, 4, 5]

# 此时列表还剩下 3 个元素
print(my_list) # [3, 4, 5]

调用 pop 方法删除:



my_list = [1, 2, 3, 4, 5]
print(my_list)

# 删除第 1 个元素
# 删除的元素可以不接收,直接忽略
my_list.pop(0)

# 删除最后 1 个元素
# 删除的元素可以接受,赋值到变量
ele = my_list.pop(0)
print(ele) # 2

print(my_list) # [3, 4, 5]
  • 如果删除的元素在下文中需要使用,调用 pop 方法
  • 如果删除的元素不再使用,调用 del 方法

2. 删除最后一个元素

直接调用 pop 方法即可。



my_list = [1, 2, 3, 4, 5]
print(my_list)

# 删除最后 1 个元素
# 删除的元素可以不接收,直接忽略
my_list.pop()

# 删除最后 1 个元素
# 删除的元素可以接受,赋值到变量
ele = list.pop()
print(ele) # 4

print(my_list) # [1, 2, 3]

3. 根据值删除元素

直接调用 remove 方法即可。



my_list = [1, 2, 3, 4, 5]
print(my_list)

my_list.remove(1)
my_list.remove(2)

print(my_list) # [3, 4, 5]

需要注意的是,对于列表中的重复值,remove 方法每次只删除第一个值,如果需要全部删除,需要多次调用。



my_list = [1, 1, 3, 4, 5]
print(my_list)

my_list.remove(1)
print(my_list) # [1, 3, 4, 5]

my_list.remove(1)
print(my_list) # [3, 4, 5]

下面是等价实现的循环代码:



my_list = [1, 1, 3, 4, 5]

while 1 in my_list:
    my_list.remove(1)
    
print(my_list) # [3, 4, 5]

排序

  • sort() 方法: 永久排序
  • sorted() 函数: 临时排序

下面是一个永久排序的小例子:



my_list = [1, 2, 3, 4, 5]
my_list.sort()

# 默认情况下是升序排序
print(my_list) # [1, 2, 3, 4, 5]

# 如果希望倒序排序
# 需要传递参数 reverse=True
my_list.sort(reverse=True)

print(my_list) # [5, 4, 3, 2, 1]

下面是一个临时排序的小例子:



my_list = [1, 2, 3, 4, 5]

# 默认情况下是升序排序
print(sorted(my_list)) # [1, 2, 3, 4, 5]

# 如果希望倒序排序
# 需要传递参数 reverse=True
print(sorted(my_list, reverse=True)) # [5, 4, 3, 2, 1]

反转

直接调用 reverse 方法即可。



my_list = [1, 2, 3, 4, 5]

my_list.reverse()

print(my_list) # [5, 4, 3, 2, 1]

遍历



my_list = [1, 2, 3, 4, 5]

# 注意 for 语句的末尾需要加一个 : 分号
for v in my_list:
    print(v * 100)

# 需要注意的是: 
# for 循环中的临时变量 v 在循环结束之后,依然有效
print(v) # 5

等价于下面的代码



for v in range(1, 6):
    print(v * 100)

# 需要注意的是: 
# for 循环中的临时变量 v 在循环结束之后,依然有效
print(v) # 5

还有一点需要注意的是: 循环时会将列表中的元素的值,赋值给临时变量,所以即使修改,也不会影响到列表元素值。



my_list = [1, 2, 3, 4, 5]

for v in my_list:
    v *= 100

# 需要注意的是: 
# for 循环中的临时变量 v 在循环结束之后,依然有效
print(v) # 5

# 循环中修改的是临时变量 v 的值
# 所以列表元素的值不受任何影响
print(my_list) # [1, 2, 3, 4, 5]

直接转换数字列表

可以通过使用函数 list()range() 的结果直接转换为数字列表。



my_list = list(range(1, 6))
print(my_list)  # [1, 2, 3, 4, 5]

# range 函数的第 3 个参数可以指定步长
# 指定生成列表的每次增加 2
my_list = list(range(1, 11, 2))
print(my_list)  # [1, 3, 5, 7, 9]

通过组合使用函数 list()range(), 可以灵活生成各种类型和规则的数字列表。



# 每个数字递增 10
my_list = list(range(10, 51, 10))
print(my_list)  # [10, 20, 30, 40, 50]

# 从 0 开始
my_list = list(range(6))
print(my_list)  # [0, 1, 2, 3, 4, 5]

# 递减列表
my_list = list(range(5, 0, -1))
print(my_list)  # [5, 4, 3, 2, 1]

# 包含负数的列表
my_list = list(range(-3, 3, 1))
print(my_list)  # [-3, -2, -1, 0, 1, 2]

计算操作



# 每个数字递增 10
my_list = list(range(6))
print(max(my_list)) # 5
print(min(my_list)) # 0
print(sum(my_list)) # 15

# 检查列表中的所有元素是否都为 True
# 非零数字被视为 True
print(all(my_list)) # False

列表解析操作

在创建列表的同时,就对列表中的元素进行处理,等于是利用语法糖的形式简化了代码实现。



# 1. 生成 1 ~ 5 的数字列表
# 2. 计算列表中每个数字的平方,并使用平方值替换元素值
my_list = [val ** 2 for val in range(1, 6)]
print(my_list) # [1, 4, 9, 16, 25]

上面的代码实现等价于下面的代码:



my_list = [1, 2, 3, 4, 5]

# enumerate(my_list): 生成一个包含索引和值的迭代器
# 每次循环中,index 是当前元素的索引,value 是当前的值
for index, value in enumerate(my_list):
    my_list[index] = value ** 2 
    
print(my_list) # [1, 4, 9, 16, 25]

当需要对列表中的所有元素执行相同的处理操作时,使用 列表解析 来简化代码。

切片

所谓切片数据结构,也就是列表的一部分数据。

同时指定左右边界索引,获取第 2 个到第 5 个元素之间的所有元素。



my_list = [1, 2, 3, 4, 5]

print(my_list[1:4]) # [2, 3, 4]

不指定左边界索引,获取第 1 个到第 5 个元素之间的所有元素。



my_list = [1, 2, 3, 4, 5]

print(my_list[:4]) # [1, 2, 3, 4]

不指定右边界索引,获取第 3 个到最后 1 个元素之间的所有元素。



my_list = [1, 2, 3, 4, 5]

print(my_list[2:]) # [3, 4, 5]

索引也可以为负数,表示倒数。

获取列表的最后 3 个数字元素:



my_list = [1, 2, 3, 4, 5]

print(my_list[-3:]) # [3, 4, 5]

获取列表除了最后 3 个元素外的所有其他元素:



my_list = [1, 2, 3, 4, 5]

print(my_list[:-3]) # [1, 2]

获取所有元素:



my_list = [1, 2, 3, 4, 5]

print(my_list[:]) # [1, 2, 3, 4, 5]

复制

和主流编程语言一样,Python 中默认的复制行为也是浅拷贝。



my_list = [1, 2, 3, 4, 5]
print(my_list) # [1, 2, 3, 4, 5]

my_list2 = my_list
print(my_list2) # [1, 2, 3, 4, 5]

my_list2[0] = 100

# 修改列表 my_list2 元素的同时
# 列表 my_list 的元素也受到了影响
# 这不是我们期望的结果
print(my_list) # [100, 2, 3, 4, 5]
print(my_list2) # [100, 2, 3, 4, 5]

如果我们希望复制列表 (深拷贝) ,可以创建一个包含整个列表元素的切片,然后进行赋值操作,就可以完成整个列表的复制 (深拷贝)。



my_list = [1, 2, 3, 4, 5]
print(my_list) # [1, 2, 3, 4, 5]

my_list2 = my_list[:]
print(my_list2) # [1, 2, 3, 4, 5]

my_list2[0] = 100

# 修改列表 my_list2 元素的同时
# 列表 my_list 的元素并没有受到任何影响
print(my_list) # [1, 2, 3, 4, 5]
print(my_list2) # [100, 2, 3, 4, 5]

元组

列表中的元素是可变的 (可以被修改/删除),如果希望列表元素不可变,可以使用元组 (不可变的列表数据结构)。

使用 () 定义即可,每个元素使用 , 分隔开。



tuple = (1, 2, 3, 4, 5)
print(tuple) # (1, 2, 3, 4, 5)

# 试图修改时报错
# TypeError: 'tuple' object does not support item assignment
tuple[0] = 100

当然,我们可以利用弱语言的特性,直接修改变量的值,可以达到修改元组元素值的效果。



tuple = (1, 2, 3, 4, 5)
print(tuple) # (1, 2, 3, 4, 5)

tuple = (10, 20, 30, 40, 50)
print(tuple) # (10, 20, 30, 40, 50)

元组是比列表更简单的数据结构,如果需要存储的元素值在程序的整个生命周期内都不变,可以使用元组。

元组和列表的遍历方式一致。



tuple = (1, 2, 3, 4, 5)

for val in tuple: 
    print(val)

字典

Python 中的字典类似于其他编程语言中的 map, HashTable, 关联数组等数据类型。

使用 {} 定义即可,每个元素使用 , 分隔开,语法格式和 JSON 很像。



languages_rank = {
    'C': 1,
    'C++': 2,
    'Java': 3,
    'Python': 4,
}

print(languages_rank) # {'C': 1, 'C++': 2, 'Java': 3, 'Python': 4}

字典中的每个 KeyValue 数据类型都可以不一样。



mixed_dict = {
    'C': 1, # string -> int
    'C++': 2.0, # string -> float
    'Java': '3', # string -> string
    4: 'Python' # int -> string
}

print(mixed_dict) # {'C': 1, 'C++': 2.0, 'Java': '3', 4: 'Python'}

访问元素

使用 [key_name] 直接访问即可。



mixed_dict = {
    'C': 1, # string -> int
    'C++': 2.0, # string -> float
    'Java': '3', # string -> string
    4: 'Python' # int -> string
}

print(mixed_dict['C']) # 1

# 访问不存在的 Key 时会报错
# print(mixed_dict['C2']) # KeyError: 'C2'

# 防御式编程
# 1. 先检测对应的 key 是否存在于字典
# 2. 然后再访问
if 'C2' in mixed_dict:
    print(mixed_dict['C2'])
else:
    print("Key C2 not in dict")


# 也可以通过字典提供的 get 方法
# 当指定的 key 不存在时,返回 None 
print(my_dict.get('C2')) # None

添加/修改元素

  • key 对应的元素存在时,修改原有的值
  • key 对应的元素不存在时,添加新值


my_dict = {
    'C': 1,
}

print(my_dict) # {'C': 1}

my_dict['C++'] = 2
my_dict['Java'] = 3
print(my_dict) # {'C': 1, 'C++': 2, 'Java': 3}

my_dict['C'] = 100
print(my_dict) # {'C': 100, 'C++': 2, 'Java': 3}

删除元素



my_dict = {
    'C': 1,
}

print(my_dict) # {'C': 1}

my_dict['C++'] = 2
my_dict['Java'] = 3
print(my_dict) # {'C': 1, 'C++': 2, 'Java': 3}

# 调用 del 函数删除
# 下面两种语法等价
del my_dict['C++']
del(my_dict['Java'])

print(my_dict) # {'C': 1}

遍历

通过调用 items() 方法将字典转换为一个键值对列表,遍历的时候就可以直接访问当前元素的 key 和值。



my_dict = {
    'C': 1,
    'C++': 2,
    'Java': 3,
    'Python': 4,
}

for key, val in my_dict.items():
    print("language = ", key, " rank = ", val)

通过调用 keys() 方法获取字典中的所有 key。



my_dict = {
    'C': 1,
    'C++': 2,
    'Java': 3,
    'Python': 4,
}

print(my_dict.keys()) # dict_keys(['C', 'C++', 'Java', 'Python'])

# 可以直接通过 list 函数将结果转换为一个列表
print(list(my_dict.keys())) # ['C', 'C++', 'Java', 'Python']

当然,对应的 values() 方法获取的就是字典中的所有值。



my_dict = {
    'C': 1,
    'C++': 2,
    'Java': 3,
    'Python': 4,
}


print(my_dict.values()) # dict_values([1, 2, 3, 4])

# 可以直接通过 list 函数将结果转换为一个列表
print(list(my_dict.values())) # [1, 2, 3, 4]

嵌套 (复合) 字典

通过使用基础类型 + 复合数据类型构建的字典。

例如,我们可以定义 1 个排名 Top 10 的编程语言的学习曲线字典。



# 排名数据为模拟,非真实数据
# 真实排名: https://www.tiobe.com/tiobe-index/
my_dict = {
    'name': 'Language learning curve',
    'desc': 'The learning difficulty curve of the top 10 languages',
    'year': 2024,
    'month': 8,
    'source': 'fake data',
    'langs': ['C', 'C++', 'Java', 'Python', 'Go', 'Rust', 'Ruby', 'Javascript', 'SQL', 'C#'],
    'ranks': {
        'C': 1, 
        'C++': 2, 
        'Java': 3, 
        'Python': 4, 
        'Go': 5, 
        'Rust': 6, 
        'Ruby': 7, 
        'Javascript': 8, 
        'SQL': 9, 
        'C#': 10,
    }
}

# 输出编程语言数量
print("languages count is ", len(my_dict['langs']))

# 输出每个编程语言的排名
for lang in my_dict['langs']: 
    # 格式化输出,需要 Python 3.6 及以上版本
    # f"{lang:10s}" 表示编程语言名称,占 10 个字符串的位置
    # f"{my_dict['ranks'][lang]:2d}" 表示编程语言排名,占 2 个数字的位置
    print(f"{lang:10s}", f"{my_dict['ranks'][lang]:2d}")

# 输出如下
# languages count is  10
# C           1
# C++         2
# Java        3
# Python      4
# Go          5
# Rust        6
# Ruby        7
# Javascript  8
# SQL         9
# C#         10

从上面的实例代码可以看到,Python 的字典和 JSON 的语法格式几乎一样。

key 的类型

Python 中,字典的 key 必须是 不可变 的数据类型。

因为只有值不可变,才会有固定的哈希值,通过使用不可变类型作为字典 key,才能确保 key 在字典中的唯一性和稳定性。

可以作为 key 的数据类型

  • 整型
  • 浮点型
  • 字符串
  • 布尔值
  • 元组
  • frozenset (不可变集合)


my_dict = {
    1: 1, # 整型 key
    1.23: 4.56, # 浮点型 key 
    'one': '1', # 字符串 key 
    True: 1, # 布尔值 key 
    (1, 2, 3): [1, 2, 3], # 元组类型 key 
    frozenset([1, 2, 3]): [1, 2, 3] # frozenset key 
}

print(my_dict) # {1: 1, 1.23: 4.56, 'one': '1', (1, 2, 3): [1, 2, 3], frozenset({1, 2, 3}): [1, 2, 3]}

不可以作为 key 的数据类型

  • 列表
  • 字典
  • 集合
  • 用户自定义类型

列表类型作为 key 报错:



my_dict = {[1, 2]: 'list key'}
# print(my_dict) # TypeError: unhashable type: 'list'

字典类型作为 key 报错:



my_dict = {{'key': 'value'}: 'dict key'}
print(my_dict) # TypeError: unhashable type: 'dict'

集合类型作为 key 报错:



my_dict = {{1, 2}: 'set key'}
print(my_dict) # TypeError: unhashable type: 'set'

集合

集合 (Set) 类似于列表数据类型,但每个元素都必须是唯一的。

‼️重要: Set 输出时时无序的。

直接将列表类型转化为集合 (去重):



my_list = [1, 2, 3, 4, 5, 1, 1]

print(set(my_list)) # {1, 2, 3, 4, 5}

直接将字典类型转化为集合 (去重):



my_dict = {
    'C': 1,
    'C': 100,
    'C++': 2,
    'Java': 3,
    'Python': 4,
}

# Set 输出时时无序的
# 所以你的输出和这里的输出不一样,属于正常现象
print(set(my_dict)) # {'Java', 'Python', 'C', 'C++'}

数学运算

Python 集合的强大之处就是可以直接通过 布尔运算符 进行数学计算。


s1 = set([1, 2, 3, 3, 3]) 
s2 = set([3, 3, 3, 4, 5])

print(s1) # {1, 2, 3}
print(s2) # {3, 4, 5}

print(s1 | s2) # 并集 {1, 2, 3, 4, 5}
print(s1 & s2) # 交集 {3}
print(s1 - s2) # 差集 {1, 2}

不可变集合

定义关键字: frozenset

fs = frozenset([1, 2, 3, 4, 5])

fs.add(6) # AttributeError: 'frozenset' object has no attribute 'add'

函数

定义关键字: def



def say_hello():
    print('Hello World')
    
say_hello()

参数

可以传递 0 - N 个参数,除此之外,还可以设置参数默认值。



def say_hello(name):
    print('Hello', name.title())
    
say_hello('Tom') # Hello Tom

因为参数设置了默认值,所以调用函数时即使不传入参数,也可以正常输出。



def say_hello(name='Tom'):
    print('Hello', name.title())
    
# 不传入参数,以默认值为准
say_hello() # Hello Tom

# 传入参数,以参数值为准
say_hello('world') # Hello World

关键字实参

默认情况下,函数参数的传入顺序和声明顺序一一对应,但是对于拥有多个参数的函数,如果我们希望调用函数时只传入一个或某几个参数,这时就可以使用 关键字实参 的传入规则了。

‼️重要: 使用关键字实参时,确保准确地指定函数定义中的参数名称。



def say_hello(first_name, last_name):
    print('Display name:', first_name, last_name)

say_hello(first_name='Tom', last_name='Jerry') # Display name: Tom Jerry
say_hello(last_name='Tom', first_name='Jerry') # Display name: Jerry Tom

返回值



def say_hello(first_name, last_name):
    return first_name + ' ' + last_name

print(say_hello(first_name='Tom', last_name='Jerry')) # Tom Jerry
print(say_hello(last_name='Tom', first_name='Jerry')) # Jerry Tom

任意可变数量参数

定义: 参数名称前加 * 即可,表示 0 到 N 个参数。



def output(*langs):
    print(langs)

output() # ()
output('C') # ('C',)
output('C', 'C++') # ('C', 'C++')
output('C', 'C++', 'Python') # ('C', 'C++', 'Python')

可以使用 for 循环遍历可变参数列表:



def output(*langs):
    for lang in langs:    
        print(lang)

可变数量参数必须位于其他参数的后面。

def output(title, desc, *langs):
    print(title, desc, langs)

任意可变数量关键字参数

定义: 参数名称前加 ** 即可,表示 0 到 N 个已命名的参数。

利用这个特性可以将命名参数直接映射到函数中。



def build(first, last, **profile):
    """
    第 3 个参数为 可变数量关键字参数
    包含了用户的其他额外信息
    """
    
    user = {}
    user['first_name'] = first 
    user['last_name'] = last

    for key, val in profile.items():
        user[key] = val

    return user

# 前 2 个参数使用实际参数
# 第 3 个参数使用关键字参数
albert = build('albert','einstein',
                location='princeton',
                field='physics')

print(albert) # {'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'}

可变参数和不可变参数类型

前面讲字典的 key 的类型时,提到了:

Python 中的数据类型分为可变类型和不可变类型。

这条规则对于函数参数依然适用:

  • 可变类型的数据传入函数后并被修改,会改变原数据
  • 不可变类型的数据则传入函数后并被修改,不会改变原数据

Python 中不能直接操作指针,也没有显式的指针特性,所以无法直接通过指针来修改不可变类型的值。

不可变类型的本质:作为函数参数传递时不会创建新的对象 (浅拷贝),避免内存开销,从而提升性能。

不可变类型的本质:作为函数参数传递时会创建新的对象 (深拷贝),而不是原对象的引用 (指针)。

当然,对于必须要修改不可变类型值的场景,可以使用 “迂回” 的方法来解决:

  • 通过返回新值覆盖不可变类型变量
  • 通过将 不可变类型 放入 可变类型 (容器) 修改不可变类型

下面是一个 不可变类型 的例子。



def double(n):
    n *= 2
    
# 整型是不可变类型
n = 1
# 即使函数内部改变参数值,不会改变原数据值
double(n)
print(n) # 1

下面是一个 可变类型 的例子。



def double(numbers):
    for i in range(len(numbers)):
        numbers[i] *= 2 

# 列表、字典、集合、用户自定义类型 都是可变类型
my_list = [1, 2, 3, 4, 5]
# 函数内部改变参数值,会改变原数据值
double(my_list)
# 此时,原始数据已经发生变化
print(my_list) # [2, 4, 6, 8, 10]

# 如果我们原数据不会被改变
# 传入列表的复制 (深拷贝) 即可
my_list2 = [1, 2, 3, 4, 5]
double(my_list2[:])
print(my_list2) # [1, 2, 3, 4, 5]

递归



# 使用递归函数计算 fibonacci 数列
def fib(n):
    if n < 2:
        return 1
    if n == 2:
        return 1
    else:
        return fib(n-1) + fib(n-2)

# [1, 1, 2, 3, 5, 8]
print(fib(6)) # 8

类和面向对象

Python (约定俗成,并非强制规定) 类名应采用 (首字母大写的) 驼峰命名法,类的属性和方法使用下划线命名法。

类的方法如果需要传递参数,那么第一个参数必须是 self (语言强制规定)。

  • 在 类 中使用一个空行来分隔 方法
  • 在 模块 中使用两个空行来分隔 类


# 定义一个类: 人类
class Person: 
    """
    类的静态字段
    如果需要定义属于类本身的字段(而不是具体实例的字段)
    可以直接在类中定义,这些字段被称为类字段或 静态字段
    静态字段被类的所有实例共享
    
    需要注意的是,静态字段可以被修改
    但是修改后,只影响修改的实例,其他实例不受影响
    """
    
    # 所有人类共同的家园: 地球
    home = "Earth"
    
    """
    初始化方法
    相当于其他编程语言中的类的构造方法
    类的每个方法的第一个参数必须是 self
    """
    def __init__(self, name) -> None:
        """
        初始化属性
        Python 类的属性字段不需要在 class 类中显式声明和定义
        直接在 __init__ 方法中动态创建和初始化即可
        """
        self.name = name
        self.age = 3

    """
    定义方法 1
    类的每个方法的第一个参数必须是 self
    self 用于指向实例本身
    """    
    def sayHi(self):
        print('Hi, My name is', self.name)
        
    """
    定义方法 2
    类的每个方法的第一个参数必须是 self
    self 用于指向实例本身
    """    
    def sleep(self):
        print('Sleeping ...')

    """
    定义方法 3
    类的每个方法的第一个参数必须是 self
    self 用于指向实例本身
    """    
    def set_age(self, age):
        self.age = age


# 定义人类的两个具体实例
p1 = Person('Alice')
p2 = Person('Bob')

# 输出两个实例的静态字段
print(p1.home) # Earth
print(p2.home) # Earth

# 修改实例 1 的静态字段
# 实例 1 的静态字段不受任何影响
p1.home = "Mars"

print(p1.home) # Mars
print(p2.home) # Earth

# 调用实例的方法
p1.sayHi() # Hi, My name is Alice
p2.sayHi() # Hi, My name is Bob

# 访问不存在的属性时会报错
# print(p1.hobby) # AttributeError: 'Person' object has no attribute 'hobby'

修改属性值

1. 直接修改

p1.age = 10
print(p1.age) # 10

2. 通过方法修改

p1.set_age(10)
print(p1.age) # 10

公共/私有作用域

Python 中,类的属性和方法默认是公开的(public), 和主流编程语言使用关键字(public/protected/private)进行访问控制不同,Python 通过命名约定来定义公开、受保护、私有的属性和方法,但是 受保护机制 并不是语言强制规定的,而是约定俗成的规定,所以即使违反规则,也并不会产生运行时错误。

  • 公开: 没有下划线前缀的属性和方法,任何地方都可以访问
  • 私有: 以单下划线 _ 开头的属性和方法,仅在类内部可以访问
  • 受保护: 以双下划线 __ 开头的属性和方法,仅在类内部及子类中可以访问
class ClassName:
    ...

下面是一个示例代码。



class Person: 
    """
    初始化方法
    """
    def __init__(self, name) -> None:
        """
        初始化属性
        """
        self.name = name
        
        # 公开属性
        self.age = 3
        
        # 受保护属性
        self._salary = 100
        
        # 私有属性
        self.__id = 1234567890
        
    # 公开方法
    def sayHi(self):
        print('Hi, My name is', self.name)

    # 受保护方法
    def _get_salary(self):
        print("This is a protected method")
        
    # 私有方法
    def __get_id(self):
        print("This is a private method") 
        
    # 通过公开访问访问私有属性/方法
    def get_id(self):
        self.__get_id()

# 定义人类的两个具体实例
p1 = Person('Alice')

# 公开属性/方法 在任何地方都可以访问
print(p1.age)
p1.sayHi()

# 受保护属性/方法 也可以正常访问
# 所以说,只是约定俗成的 (软) 规范
print(p1._salary) # 100
p1._get_salary() # This is a protected method

# 私有属性/方法 只能在类内部访问
# 是 Python 语言的强制性规定
# 在类外部访问时直接报错
# print(p1.__id) # AttributeError: 'Person' object has no attribute '__id'
# p1.__get_id() # AttributeError: 'Person' object has no attribute '__get_id'

# 但是通过 公有方法 访问 私有属性/方法 是 OK 的
p1.get_id() # This is a private method

# 除此之外,Python 提供了一种名称重整(name mangling)机制
# Python 会将私有的属性和方法的名称修改为 _ClassName__attributeName 的形式
# 可以通过 _ClassName__attributeName 的形式来访问类的私有属性/方法
# 这种做法通常不推荐,因为它违背了私有属性和方法的设计初衷

# 访问私有属性,可以正常运行,但是不推荐
print(p1._Person__id) # 1234567890
# 调用私有方法,可以正常运行,但是不推荐
p1._Person__get_id() # This is a private method

继承

Python 和其他主流编程语言中的继承机制规则一样:

  • 一个类继承另一个类时,它将自动获得另一个类的所有属性和方法
  • 原有的类称为父类,而新类称为子类
  • 子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法

基本语法格式:

class SubClass(ParentClass):
    ...

下面是一个示例代码。



# 定义父类: 动物
class Animal: 
    """
    父类初始化方法
    """
    def __init__(self, name) -> None:
        # 父类初始化属性
        self.name = name
        
    # 公开方法
    def speak(self):
        print(f"{self.name} makes a sound")
        

# 定义子类: 猫
class Cat(Animal): 
    """
    子类初始化属性
    """
    def __init__(self, name) -> None:
        # 子类初始化父类的属性
        # 关键字 super() 表示父类
        super().__init__(name)
        
        # 子类初始化自己的属性
        self.age = 3
        self.friends = ['Jerry']

    """
    子类定义自己的方法
    """
    def sayAge(self):
        print(f"I'm {self.age} years old")

    """
    子类重写 (Overriding) 父类的方法
    """
    def speak(self):
        print(f"My name is {self.name}")
        

# 实例化一个父类
duck = Animal('Duck')
duck.speak() # self.age

# 实例化一个子类
tom = Cat('Tom')
tom.speak() # My name is Tom
tom.sayAge() # I'm 3 years old

多重继承

Python 支持多重继承,一个类可以继承多个父类,多个父类的类名在子类的定义中用逗号分隔。

基本语法格式:

class SubClass(ParentClass1, ParentClass2 ...):
    ...

下面是一个示例代码。



class A:
    def method_a(self):
        print("Method A")

class B:
    def method_b(self):
        print("Method B")

class C(A, B):
    def method_c(self):
        print("Method C")

c = C()
c.method_a()  # Method A
c.method_b()  # Method B
c.method_c()  # Method C

多态

多态机制: 不同的类/实例,调用相同的方法,得到不同的结果。

Python 中的多态的实现基于继承和方法重写(Overriding), 不同的类实现了同样的方法名,在调用这些方法时,会根据对象的实际类型执行对应的方法。



# 定义父类 (基类)
class Animal:
    # 基类中定义一个通用方法
    def speak(self):
        pass  

# 定义子类 (狗)
class Dog(Animal):
    def speak(self):
        print('Woof!')

# 定义子类 (猫)
class Cat(Animal):
    def speak(self):
        print('Meow!')

# 定义子类 (牛)
class Cow(Animal):
    def speak(self):
        print('Moo!')

# 函数不关注 参数类型具体属于哪个类
# 直接调用其方法
def animal_sound(animal):
    animal.speak()

# 创建不同类型的实例
dog = Dog()
cat = Cat()
cow = Cow()

# 不同的类/实例,调用相同的方法,得到不同的结果
animal_sound(dog)  # Woof!
animal_sound(cat)  # Meow!
animal_sound(cow)  # Moo!

抽象类/方法

Python 中提供了 abc 模块 (名字就是这么随意~) 来定义抽象类,确保子类必须实现某些方法。


from abc import ABC, abstractmethod

# 父类继承自抽象类 ABC
class Animal(ABC):
    """
    使用 @abstractmethod 装饰器标记 speak 方法为抽象方法
    所有子类必须实现这个方法
    """
    @abstractmethod
    def speak(self):
        pass

# 定义子类 (狗)
class Dog(Animal):
    def speak(self):
        print('Woof!')

# 定义子类 (猫)
class Cat(Animal):
    def speak(self):
        print('Meow!')

# 定义子类 (牛)
class Cow(Animal):
    def speak(self):
        print('Moo!')

# 函数不关注 参数类型具体属于哪个类
# 直接调用其方法
def animal_sound(animal):
    animal.speak()

# 创建不同类型的实例
dog = Dog()
cat = Cat()
cow = Cow()

# 不同的类/实例,调用相同的方法,得到不同的结果
animal_sound(dog)  # Woof!
animal_sound(cat)  # Meow!
animal_sound(cow)  # Moo!

静态方法

静态方法可以通过类、实例直接访问。


# 定义父类 (基类)
class Animal:
    # 基类中定义一个通用方法
    def speak(self):
        pass  
    
    """
    使用 @staticmethod 装饰器标记 help 方法为静态方法
    """
    @staticmethod
    def help():
        print('help is static method')

# 定义子类 (狗)
class Dog(Animal):
    def speak(self):
        print('Woof!')

Animal.help() # help is static method

# 创建实例
dog = Dog()
dog.help() # help is static method

异常

Python 支持通过异常处理程序运行时发生的错误。

关键字: try, except, finally



# 除数不能为 0
# print(1/0) # ZeroDivisionError: division by zero

# 如果 try 代码块中的代码正常运行
#    跳过 except 代码块
# 如果 try 代码块中的代码运行发生错误
#    执行 except 代码块的代码
# 无论 try 和 except 是否发生运行异常
# finally 代码块中代码 (最后) 都会执行
try:
    print(1/0)
except ZeroDivisionError:
    print('divide by zero')
finally: 
    print('finally ...')

Python 还支持异常处理和 else 逻辑表达式组合使用,try 代码块成功执行后的下文代码,都应放到 else 代码块中。



# 如果 try 代码块中的代码正常运行
#    跳过 except 代码块
#    执行 else 代码块中的代码
# 如果 try 代码块中的代码运行发生错误
#    执行 except 代码块的代码
# 无论 try 和 except 是否发生运行异常
# finally 代码块中代码 (最后) 都会执行
try:
    answer = 10 / 3
except ZeroDivisionError:
    print('divide by zero')
else:
    # 如果可以正常计算,则输出
    print(answer)
finally: 
    print('finally ...')

模块

Python 的模块系统非常灵活,可以是一个文件,也可以是多个文件,具体取决于模块的实现方式。

模块导入关键字: import

单个文件的模块

最常见的情况是,一个 Python 模块就是一个以 .py 结尾的单个文件。

这个文件中包含了函数、类、变量等 Python 代码,模块名与文件名一致(不包括 .py 扩展名)。

  • 假设当前执行文件为 main.py
  • 在同级目录,新增一个模块文件 hello.py

将下面的代码写入到 hello.py 文件中:



def say():
    print('hello world')

将下面的代码写入到 main.py 文件中:


import hello

hello.say() # hello world

多个文件的模块

如果模块由多个文件组成,这个模块被称为

包是一个包含多个模块的目录,并且这个目录中通常包含一个特殊的 __init__.py 文件,这个文件可以是空的,也可以包含初始化包时运行的代码 (导入时执行)。

假设有以下包 (多个模块) 目录结构:

person
├── __init__.py
├── act.py
└── profile.py

上述目录结构中,person 是 1 个包,它包含了 2 个模块 actprofile

# __init__.py
print('package init')

# act.py
def say(name):
    print("Hi, I'm", name.title())
    
def run():
    print('Running')

1. 导入整个包



import person

可以将整个包导入,如果 __init__.py 文件中定义了初始化代码,会直接执行。

2. 导入单个模块

语法格式如下:

from package_name import module_name

将下面的代码写入到 main.py 文件中:



from person import act

act.say('Tom')

3. 导入单个模块的指定函数

语法格式如下 (可以导入 1 个或多个方法):

from package_name.module_name import function_name_1, function_name_2 ...

将下面的代码写入到 main.py 文件中:



from person.act import say

say('Tom')

4. 使用 as 为函数指定别名

当一个包中的多个模块中存在相同的方法名时,可以使用 as 关键字为同名方法指定别名。

语法格式如下:

from package_name.module_name import function_name as function_name

将下面的代码写入到 main.py 文件中:



from person.act import say as speak

speak('Tom')

5. 导入类相关

导入类和导入函数语法基本一致,具体实例代码不再一一赘述。

这里以包 (模块由多个文件组成) 为示例进行语法说明。

假设有以下包 (多个模块) 目录结构:

animal
├── __init__.py
├── animal.py
├── cat.py
└── dog.py
# 导入 cat 模块中的所有类
from animal.cat import *

# 导入 cat 模块中的 Cat 类
from animal.cat import Cat

# 导入 cat 模块中的 Cat, Cat2 类
from animal.cat import Cat, Cat2

‼️需要注意的是:类和函数一样,是模块导入的最小单位。


单元测试

Python 提供一个单元测试库 unittest

  • 测试通过时输出 1 个 .
  • 测试错误时输出 1 个 E
  • 测试失败时输出 1 个 F

新建 2 个文件,calc.py, test_calc.py

  • calc.py: 模块代码文件
  • test_calc.py 模块单元测试代码文件
# 目录结构如下

.
├── calc.py
└── test_calc.py

将以下代码写入 calc.py 文件中:

def add(x, y):
    return x + y

def multi(x, y):
    return x * y 

将以下代码写入 test_calc.py 文件中:

import unittest
import calc

""""
TestCalc 类继承自 unittest.TestCase 类
专门用于测试 calc 模块中的函数
每个测试方法都以 test_ 前缀开头
""" 
class TestCalc(unittest.TestCase):
    """
    测试 add 函数
    """
    def test_add(self):
        self.assertEqual(calc.add(1, 2), 3)
        
    """
    测试 add 函数
    """
    def test_multi(self):
        self.assertEqual(calc.multi(1, 2), 2)

"""
unittest.main() 方法执行当前模块中所有测试用例
"""
if __name__ == '__main__':
    unittest.main()

执行单元测试代码:

# 测试文件中的所有模块
$ python test_calc.py

# 输出如下
# 2 个测试方法通过,输出 2 个 .
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

除了测试所有模块外,也可以测试指定的模块/测试类/测试函数。

# 指定测试某个模块
$ python -m unittest calc_test

# 指定测试某个测试类
$ python -m unittest calc_test.TestCalc

# 指定测试某个测试类的方法
$ python -m unittest calc_test.TestCalc.test_add

测试钩子函数

import unittest
import calc


""""
TestCalc 类继承自 unittest.TestCase 类
专门用于测试 calc 模块中的函数
每个测试方法都以 test_ 前缀开头
""" 
class TestCalc(unittest.TestCase):
    """
    测试 add 函数
    """
    def test_add(self):
        self.assertEqual(calc.add(1, 2), 3)
        
    """
    测试 add 函数
    """
    def test_multi(self):
        self.assertEqual(calc.multi(1, 2), 2)
        
        
    @classmethod
    def setUpClass(calc):
        print('所有测试用例开始前执行')
    
    @classmethod
    def tearDownClass(calc):
        print('所有测试用例结束后执行')

    def setUp(self):
        print('单个测试用例开始前执行')
    
    def tearDown(self):
        print('单个测试用例结束后执行')


"""
unittest.main() 方法执行当前模块中所有测试用例
"""
if __name__ == '__main__':
    unittest.main()

执行单元测试代码:

# 测试文件中的所有模块
$ python test_calc.py

所有测试用例开始前执行
单个测试用例开始前执行
单个测试用例结束后执行
.单个测试用例开始前执行 # 测试通过输出 .
单个测试用例结束后执行
.所有测试用例结束后执行 # 测试通过输出 .

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

OK

API 速查表

在线: https://overapi.com/python

扩展阅读