蛮荆

Python 快速入门

2020-01-24


前言

人生苦短,我用 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

扩展阅读

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。