Python官方文档网站

多读读能更加了解Python的各种语法与相关特性,有中文版的,碰到不懂的语法问题也可以上去查找一下。

Python官方文档

Python文档

导包(import)

包与模块

import 用于将其他模块(module)或包(package)中的代码引入到当前文件中使用,从而复用其他模块 or 包的功能。

什么是模块(module),什么是包(package)

包与模块 包内部的模块

模块与包的关系,可以类比为文件和目录的关系,模块相当于目录下的文件,目录相当于包

module 内部包含变量、函数或类的代码,这些代码属于 module 定义的命名空间的一部分,两个不同的module可以有相同的变量名、函数或类。package 里面存放的是一个个module,也可以有子包(sub-package)。一个module定义一个命名空间,以便变量、函数和类可以在两个不同的module中具有相同的名称(很少),通过点号.访问主包中的module和子包,包目录下通常会有一个特殊的 __init__.py 文件,表名这个目录是一个Python包。

package结构示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 包目录结构示例:
package/
├── __init__.py
├── utils/
│ ├── __init__.py
│ └── helper.py
└── data/
├── __init__.py
└── processor.py

# 在其他文件中,这样导入
from package.utils import helper
helper.some_function()

from package.data import processor
processor.do_something()

python导包知识

__init__.py的作用

  • 将目录标识为包(package)__init__.py 是一个特殊的文件,用来标志一个目录是 python 包,让 Python 解释器知道这个目录下的模块(.py 文件)可以被导入(import)。如果没有__init__.py,目录只会被视为一个普通的文件夹,无法从这个目录中导入已有的python模块。__init__.py可以为空,但它的存在至关重要,起到标识作用。

  • 初始化代码:**__init__.py 中的代码在包被导入时会自动执行**。可以用来做一些初始化工作,如导包时打印信息,或者设置包级别的变量、导入子模块等。

  • 控制包的导入行为:可以在 __init__.py 中明确地暴露包中的某些模块或对象,从而简化导入的语法。如下面的代码段(二)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 假设目录结构:
my_project/
|-- my_package/
| |-- __init__.py
| |-- module_a.py
| |-- module_b.py
|-- main.py


# 如果 my_package 目录下没有 __init__.py 文件,那么在 main.py 中尝试导入 my_package 将会失败,Python 解释器不知道 # my_package 是一个包,所以无法找到它里面的模块
# main.py
import my_package.module_a

# 运行 main.py
# 将会报错:ModuleNotFoundError: No module named 'my_package'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 利用__init__.py简化导入

my_project/
|-- my_package/
| |-- __init__.py
| |-- module_a.py # 假设module_a.py包含def say_hello()
| |-- module_b.py # 假设module_b.py包含def say_goodbye()
|-- main.py

# 在__init__.py文件写入如下内容
from .module_a import say_hello
from .module_b import say_goodbye

# 也可以用来控制 `from my_package import *` 的行为
__all__ = ["say_hello", "say_goodbye"]

# 这样修改后,main.py 中的导入方式就可以变得更简洁
from my_package import say_hello, say_goodbye

print(say_hello())
print(say_goodbye())
文件夹与包的标志不同

导包规则

import 的核心作用是将模块或包加载到内存中,从而提供对其中代码的访问权限。当执行 import 语句时,Python 解释器会做以下几件事:

  • 查找模块:python的所有包都从 sys.path 中获取,sys.path返回的是一个路径列表,代表Python导包时的搜素路径。这个列表包括当前工作目录、运行.py脚本所在的目录、Python 标准库安装目录和已安装的第三方库目录。
    • 当前目录:首先在执行python文件的目录及其子目录中查找。例如在 D:\Python Project\Pythonic\demo1\RAG 目录下运行 streamlit run txt_search.py,则当前目录是 RAG,Python解释器不会自动向上查找 demo1Pythonic 下的其他模块。
    • PYTHONPATH 环境变量:如果设置了 PYTHONPATH 环境变量,Python 会将其中指定的目录添加到 sys.path 中。可以用它来添加那些不在默认路径中的自定义模块目录。
    • 标准库目录:还会在安装目录下的标准库(E:\Python3.9\Lib)中搜索。这里包含了像 ossysjson 等内置模块。
    • 第三方库目录(site-packages:最后,它会在 site-packages 目录(E:\Python3.9\Lib\site-packages)中搜索。这里是 pip 安装的第三方库(如 pandasnumpy)的存放位置。
    • 如果 Python 在上述任何一个目录中找到了所需的模块文件,它就会停止搜索并加载该模块进内存。如果遍历了sys.path列表内的所有路径仍然找不到所需模块,就会抛出 ModuleNotFoundError
1
2
# sys.path包含的路径示例
['D:\\Python Project\\Pythonic\\Pythonic\\demo1\\LangGraph', 'D:\\Python Project\\Pythonic', 'E:\\PyCharm 2024.1.1\\plugins\\python\\helpers\\pycharm_display', 'E:\\anaconda3\\envs\\Juke_AI_Project\\python39.zip', 'E:\\anaconda3\\envs\\Juke_AI_Project\\DLLs', 'E:\\anaconda3\\envs\\Juke_AI_Project\\lib', 'E:\\anaconda3\\envs\\Juke_AI_Project', 'E:\\anaconda3\\envs\\Juke_AI_Project\\lib\\site-packages', 'E:\\PyCharm 2024.1.1\\plugins\\python\\helpers\\pycharm_matplotlib_backend']
  • 执行模块代码:如果找到了模块文件,解释器会从头到尾执行一遍模块中的所有代码。在这个过程中,模块中的所有变量、函数和类都会被定义并存储在一个字典中,这个字典就是该模块的命名空间

  • 缓存模块:模块只会被导入一次。首次导入后,它会被缓存到 sys.modules 字典中。后续的 import 语句如果导入的模块名与之前的相同,将直接从缓存中获取,而不会重新执行模块代码。

1
2
3
4
5
# 查看当前的模块搜索路径
import sys
import pprint

pprint.pprint(sys.path)

import导包优良习惯

  • **尽量不使用 from module import ***:避免命名空间污染,尤其是在大型项目中。因为它会使当前命名空间变得混乱,无法知道一个函数或变量是来自哪个模块,降低代码的可读性。

  • import 语句放在文件顶部:这是 PEP 8(Python 代码风格指南)的规定,可以提高代码的可读性。

  • **按标准库、第三方库和自定义模块的顺序分组 import**:这样能让代码结构更清晰。

  • 使用别名来简化长模块名或避免冲突:例如,import numpy as np

  • 使用相对导入:在包内部的模块之间导入时,可以使用 ... 来进行相对导入,例如 from . import modulefrom .. import package。这可以避免硬编码包名,使代码更具可移植性。

追加新的导包路径(稳定解决ModuleNotFoundError

出现ModuleNotFoundError时,可以在程序运行前动态追加新的导包路径;

最稳定的方法:手动修改 sys.path,确保项目根目录始终在搜索路径中。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 明确项目结构的方法
sys.path.append('D:/Python Project/Pythonic') # 追加到末尾
sys.path.insert(0, 'D:/Python Project/Pythonic') # 追加到开头位置,可以确保先搜索这个路径


# 通用方法(不清楚项目结构时)
import sys
import os

def add_project_root_to_path(root_dir_name, script_file=__file__):
"""
动态地将指定名称的项目根目录添加到 sys.path。

Args:
root_dir_name (str): 项目根目录的名称。
script_file (str): 调用此函数的脚本的路径。默认为 __file__。
"""
current_script_path = os.path.abspath(script_file)
current_dir = os.path.dirname(current_script_path)

# 循环向上查找,直到找到项目根目录或到达文件系统根目录
while os.path.basename(current_dir) != root_dir_name and current_dir != os.path.dirname(current_dir):
current_dir = os.path.dirname(current_dir)

# 检查是否找到了项目根目录
if os.path.basename(current_dir) == root_dir_name:
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
# print(f"项目根目录 '{current_dir}' 已成功添加到 sys.path。")
# else:
# print(f"警告: 未找到项目根目录 '{root_dir_name}'。")


# 假设项目根目录是 'Pythonic'
# add_project_root_to_path('Pythonic')

# 然后就可以安全地使用绝对导入了
# from demo1.model.LLM import model

if的真假判断

在 Python 中,if 语句不仅仅判断布尔值 TrueFalse。它会遵循一套规则,将任何对象评估为布尔值!。这种行为被称为真值测试

parameter 取决于什么?parameter最终的真假结果,完全取决于它所引用的那个对象的类型和内容。

if parameter: 什么时候为假(False)

一个对象parameterif 条件中被判断为假,通常是因为它“为空”或“为零”。以下这些内置数据类型会被评估为 False

  • 布尔值 False 本身

  • 数字零: 整数 0、浮点数 0.0

  • 空序列(Sequence):

    • 空字符串:""

    • 空列表:[]

    • 空元组:()

  • 空映射(Mapping):

    • 空字典:{}
  • 空集合(Set): set()

  • 特殊对象: None

if parameter: 什么时候为真(True)

如果一个对象不满足上述“为假”的条件,那么它就会被评估为 True。简单来说,任何非空、非零的对象都会被认为是真的。

  • 布尔值 True 本身

  • 任何非零的数字!!!: 1-13.14

  • 任何非空的序列:

    • 非空字符串:"hello"

    • 非空列表:[1, 2]

    • 非空元组:(1,)

  • 任何非空的映射:

    • 非空字典:{'key': 'value'}
  • 任何非空的集合: {1, 2}

字典

字典的键

在 Python 中,字典的键必须是不可变类型,因为键需要是可哈希的(hashable)。可哈希的对象在程序运行期间保持不变的哈希值,用于字典的快速查找。以下是可以作为字典键的类型(不可变类型):

  • 整数(int)
  • 浮点数(float)
  • 字符串(str)——最常用
  • 布尔值(bool): True, False
  • 元组(tuple):如 (1, “chat1”),前提是元组内的元素也必须是不可变的(不能包含列表等可变类型)。

  • 冻结集合(frozenset):如 frozenset([1, 2, 3]),不可变的集合类型。

  • 自定义对象:如果自定义类实现了 hash() 方法且是不可变的,可以作为键。

字典的键 不能是可变类型,因为可变对象的哈希值可能在运行时改变,导致字典无法正确查找键。以下是不能作为字典键的类型:

  • 列表(list):如 [1, 2, 3],因为列表可以修改(如添加、删除元素)。

  • 集合(set):如 {1, 2, 3},set 本身是可变的,可以添加(add)或移除(remove)元素,但集合中的元素必须是不可变(可哈希)的数据类型,跟字典的键要求一样!

  • 字典(dict):如 {“a”: 1},因为字典本身是可变的。

  • 可变自定义对象:如果对象的内容可以改变(如修改属性),则不能作为键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 可以作为键
d = {
1: "integer",
"key": "string",
(101, "chat1"): "tuple",
True: "boolean",
frozenset([1, 2]): "frozenset"
}
print(d[1]) # 输出: integer
print(d[(101, "chat1")]) # 输出: tuple

# 不能作为键
try:
d[{1, 2}] = "set" # TypeError: unhashable type: 'set'
except TypeError as e:
print(e)

try:
d[[1, 2]] = "list" # TypeError: unhashable type: 'list'
except TypeError as e:
print(e)

上下文管理器

什么是上下文管理器

一句话来说,上下文管理器就是一个对象,它可以在代码运行前自动执行一些准备工作,在运行完后自动执行一些收尾清理工作。 这就像 HTML 里的 <div> ... </div>,用 with 来保证结构完整,不会少写 </div>,能在进入和退出时自动执行逻辑。最常见的形式是一个类(Class)。也可以通过函数形式出现。上下文管理器语法如下:

with ... as ...:

1
2
3
4
5
6
7
8
9
10
11
# 传统写法
f = open("data.txt", "w")
f.write("hello")
f.close() # 必须手动关闭,否则可能资源泄漏

# 上下文管理器写法
with open("data.txt", "w") as f:
f.write("hello")
# 离开 with 代码块时,文件会自动关闭
# 不用担心忘记 close()。
# 出现异常也能保证关闭文件。

上下文管理器的工作原理

本质上,它依赖两个特殊方法:__enter____exit__,在 Python 里,要判断一个对象是不是上下文管理器,主要看它是否实现了 “上下文协议”

  • __enter__:进入 with 语句时自动调用

  • __exit__:离开 with 语句时自动调用(无论是否异常)

  • 同步上下文管理器:必须有 __enter____exit__ 两个方法。

  • 异步上下文管理器:必须有 __aenter____aexit__ 两个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyContext:
def __enter__(self):
print("进入上下文")
return "资源" # 这个值赋给 as 后面的变量
def __exit__(self, exc_type, exc_value, traceback):
print("退出上下文,清理资源")

# 使用
with MyContext() as res:
print("处理中...", res)

# 输出:
进入上下文
处理中... 资源
退出上下文,清理资源

常见的上下文管理器

  1. 文件/IO(open、gzip、zipfile…)
  2. 并发锁(threading.Lock、asyncio.Lock…)
  3. 网络/数据库(socket、数据库连接、requests.Session…)
  4. 临时配置(decimal.localcontext、numpy.printoptions…)
  5. 异常/输出控制(suppress、redirect_stdout…)
  6. 框架专用(Streamlit 的 chat_message、expander 等)

函数的位置参数与关键字参数

位置参数关键字参数 是两种常见的函数传参方式。

位置参数(Position Parameter)

  • 位置参数是按照函数定义参数的顺序传递的
  • 传递参数时,顺序必须匹配函数的参数顺序
  • 如果没有给位置参数赋予默认值,则调用函数时必须提供位置参数,否则会报错
1
2
3
4
5
6
7
8
9
# 示例
def greet(name, age):
print(f"Hello, my name is {name} and I am {age} years old.")

greet("Alice", 25) # 位置参数,按顺序匹配 name="Alice", age=25

# 如果参数顺序错误,可能导致错误或意外的结果。
greet(25, "Alice") # 结果不正确
Hello, my name is 25 and I am Alice years old.

关键字参数(Keyword Parameter)

  • 关键字参数通过参数名来传递的值,可以不考虑参数的书写顺序,只要参数名称匹配得上即可
  • 允许改变参数顺序,不会影响函数的执行结果。
  • **kwargs 在不同的函数中是独立的。它扮演着两种角色:
    1. 打包(Pack):在函数定义时,**kwargs 将所有未匹配的关键字参数收集到一个字典中。
    2. 解包(Unpack):在函数调用时,**kwargs 将一个字典中的键值对解开,并作为独立的关键字参数传递。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def helper_func(name, **kwargs):
# **kwargs 会收集所有多余的关键字参数
print(f"在 helper_func 内部...")
print(f"name: {name}")
print(f"kwargs: {kwargs}")
print("-" * 20)

def main_func(**kwargs):
# main_func 的 **kwargs 会收集从外部传入的所有关键字参数
print("在 main_func 内部...")
print(f"kwargs: {kwargs}")
print("-" * 20)

# 然后,main_func 会将这些参数解包并传递给 helper_func
helper_func(**kwargs)

# 调用主函数
main_func(name="Alice", age=30)
================================
1. 调用 main_func
main_func 函数的 **kwargs 会收集所有关键字参数,并将其打包成一个字典。
此时,main_func 内部的 kwargs 变量就是 {'name': 'Alice', 'age': 30}。

2. main_func 调用 helper_func
在 main_func 内部调用 helper_func(**kwargs),**kwargs 会解包刚刚打包好的字典。
将 {'name': 'Alice', 'age': 30} 字典中的每一项都作为独立的关键字参数传给 helper_func,
这个调用实际上等同于 helper_func(name='Alice', age=30)。

3. 进入 helper_func
现在,helper_func 接收到了 name='Alice' 和 age=30 这两个参数。
helper_func 函数定义中的 name 参数会接收到 'Alice' 这个值。
helper_func 函数定义中的 **kwargs 会再次收集所有多余的关键字参数。在这里,age=30 是一个多余的关键字参数(因为它没有显式地定义在 helper_func 的参数列表中),所以它会被打包进 helper_func 的 kwargs 字典中。
此时,helper_func 内部的 kwargs 变量是 {'age': 30}。
1
2
greet(age=25, name="Alice")  # 顺序无关紧要
Hello, my name is Alice and I am 25 years old.

位置参数与关键字参数混用

位置参数必须写在前面,关键字参数写在后面,关键字参数后面不能有位置参数,否则会报错。

1
2
3
4
5
# 正确
greet("Alice", age=25) # 位置参数 + 关键字参数
# 错误
greet(name="Alice", 25) # 关键字参数后面不能有位置参数
SyntaxError: positional argument follows keyword argument

默认参数

为关键字参数设置默认值,使其成为可选参数,用户未给默认参数赋值时使用默认值,如果用户在调用函数的同时给默认参数赋予了值,则采用用户赋予的新值

1
2
3
4
5
6
def greet(name, age=18):  # 不设置类型
def greet(name, age:int=18): # 设置默认类型
print(f"Hello, my name is {name} and I am {age} years old.")

greet("Bob") # 没提供 age,默认值生效
greet("Alice", 25) # 提供了 age,使用 25

函数参数列表中的*符号含义

在 Python 函数的参数列表中,*(单独的星号)是一个特殊的语法,它表示强制关键字参数,意思是它后面的所有参数都必须以关键字参数的形式传递,不能以位置参数的形式传递。* 之前的参数,既可以作为位置参数传递,也可以作为关键字参数传递,在 * 之后的参数,只能作为关键字参数传递,包括**kwargs

例如:

1
2
# 传入示例
PromptTemplate.from_template("Hello {name}", template_format="f-string", default_value="world")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@classmethod
def from_template(
cls,
template: str,
*, # <------这里的`*`就代表后面的template_format和 partial_variables都必须是关键字参数
template_format: PromptTemplateFormat = "f-string",
partial_variables: Optional[dict[str, Any]] = None,
**kwargs: Any,
) -> PromptTemplate:
input_variables = get_template_variables(template, template_format)
partial_variables_ = partial_variables or {}

if partial_variables_:
input_variables = [
var for var in input_variables if var not in partial_variables_
]

return cls(
input_variables=input_variables,
template=template,
template_format=template_format,
partial_variables=partial_variables_,
**kwargs,
)

总结

位置参数与关键字参数

*args与**kwargs

在 Python 中,*args**kwargs 均被称为参数收集器或参数解包器,用于定义函数时接收可变数量的参数。可以将不定数量的参数传递给一个函数,提高函数接受参数时的灵活性,使其能够处理未知数量的输入参数。

1
2
3
4
5
6
7
8
9
10
11
12
def example_function(a, b, *args, c=10, **kwargs):
print(f"a = {a}, b = {b}")
print(f"args = {args}") # 额外的位置参数
print(f"c = {c}") # 关键字参数,带默认值
print(f"kwargs = {kwargs}") # 额外的关键字参数

example_function(1, 2, 3, 4, c=20, x=30, y=40)

a = 1, b = 2
args = (3, 4)
c = 20
kwargs = {'x': 30, 'y': 40}

*args

* 表示解包,args 是一个约定俗成的名称(argsarguments 的缩写,名称可以自定义如 *numbers),但通常用 args。允许函数接受任意数量的位置参数,并将参数打包成一个元组(tuple)传递给函数内部。在函数内部,可以使用 args 来遍历访问获取这些参数。这意味着可以传递任意数量的位置参数给函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 示例
def test_var_args(f_arg, *args):
print("first normal arg:",f_arg)
for arg in args:
print("another arg through *args:",arg)
test_var_args('yasoob','python','eggs','test')

# 输出结果
first normal arg: yasoob
another arg through *args: python
another arg through *args: eggs
another arg through *args: test

# 示例2 定义一个打印的函数,传入任意参数即可
def print_func(*args):
print(type(args))
print(args)
print_func(1,2,'python',[])

# 输出结果
<class 'tuple'>
(1, 2, 'python', [])

# 示例3 在打印函数的参数处,新增 x 和 y 变量
def print_func(x,y,*args):
print(type(x))
print(x)
print(y)
print(type(args))
print(args)
print_func(1,2,'python',[])

# 输出结果
<class 'int'>
1
2
<class 'tuple'>
('python', [])

**kwargs

**kwargskeyword arguments 的缩写,表示关键字参数。允许将不定长度的键值对 key-value ,作为参数传递给一个函数。如果想要在函数里处理带名字的参数,应该使用**kwargs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 示例1
def print_func(**kwargs):
print(type(kwargs))
print(kwargs)

print_func(a=1, b=2, c='呵呵', d=[])

# 输出结果
<class 'dict'>
{'a': 1, 'b': 2, 'c': '呵呵', 'd': []}

# 示例2
def greet_me(**kwargs):
for key, value in kwargs.items():
print("{0} == {1}".format(key, value))
greet_me(name="yasoob")

# 输出
name == yasoob

总结

arg,*args,**kwargs ,三者是可以组合使用的,但是组合使用需要遵循一定的语法规则,即顺序为王。使用时要求 *args 参数必须在** kwargs 前面 【因为位置参数需要在关键字参数的前面。】

  • *args 用于接收任意数量的位置参数,返回一个元组。
  • **kwargs 用于接收任意数量的关键字参数,返回一个字典。
  • 在函数定义中可以同时使用 *args**kwargs 来提高灵活性。
  • *** 还可以在函数调用时解包序列(列表、字典等)。
1
2
3
4
5
6
7
8
9
10
11
def print_func(x, *args, **kwargs):
print(x)
print(args)
print(kwargs)

print_func(1, 2, 3, 4, y=1, a=2, b=3, c=4)

# 输出结果
1
(2, 3, 4)
{'y': 1, 'a': 2, 'b': 3, 'c': 4}

列表切片

Python 中的切片操作是一种非常强大且常用的功能,可以从列表 (list)、元组 (tuple)、字符串 (str) 等序列类型中提取出子序列。基本语法是:

sequence[start:stop:step] 包含开始start,不包含结束stop —> [start, stop)

  • sequence:要进行切片操作的序列(例如列表、元组、字符串)。

  • start:切片开始的索引包含)。如果省略,默认为序列的开头(索引 0)。

    • 正向索引:从 0 开始计数。
    • 负向索引从 -1 开始计数,-1 表示最后一个元素,-2 表示倒数第二个元素,以此类推。
  • stop:切片结束的索引不包含)。如果省略,默认为序列的末尾。

  • step:步长,表示每隔多少个元素取一个。如果省略,默认为 1。

对列表进行切片,返回的仍然是新列表

对元组进行切片,返回的仍然是新元组

对字符串进行切片,返回的仍然是新字符串

示例

1
L = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
操作 语法 结果 解释
取前 N 个 L[:3] ['A', 'B', 'C'] 从开头(索引 0)到索引 3 之前(不包含 3)。
取中间一段 L[1:4] ['B', 'C', 'D'] 从索引 1 到索引 4 之前。
取后 N 个 L[-3:] ['E', 'F', 'G'] 从倒数第 3 个元素到末尾。负数索引从末尾开始计数。
倒数切片 L[-4:-1] ['D', 'E', 'F'] 从倒数第 4 个元素到倒数第 1 个元素之前。
指定步长 L[::2] ['A', 'C', 'E', 'G'] 从头到尾,每隔 2 个取一个。
复制序列 L[:] ['A', 'B', 'C', 'D', 'E', 'F', 'G'] 复制整个序列(浅拷贝)。
反转序列 L[::-1] ['G', 'F', 'E', 'D', 'C', 'B', 'A'] 步长为 -1,可以快速反转序列。

利用切片对数组进行增删改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 修改元素:
nums = [1, 2, 3, 4, 5]
nums[1:4] = [10, 20, 30] # 用新列表替换索引 1 到 4 之间的元素
# nums 变为 [1, 10, 20, 30, 5]

# 删除元素:
nums = [1, 2, 3, 4, 5]
nums[1:4] = [] # 用空列表替换,相当于删除
# nums 变为 [1, 5]

# 或者使用 del 关键字
# del nums[1:4]

# 插入元素:
nums = [1, 2, 3, 4, 5]
nums[1:1] = [10, 20, 30] # 在索引 1 处插入新元素 (1:1 表示一个空切片)
# nums 变为 [1, 10, 20, 30, 2, 3, 4, 5]

深拷贝与浅拷贝

=的作用

在python中,赋值操作符(=并不会创建新对象,而是创建了一个新的变量名,它指向与原变量同一个对象,两个变量的内存地址是相同的,无论是改变新变量名的值还是旧变量名的值,都会导致它们指向的内存地址上的值发生变化!

1
2
3
4
5
6
7
8
9
10
11
original = [1, 2, 3]
alias = original

# 验证:它们指向同一个对象
print(f"original ID: {id(original)}") # original ID: 140735674123456
print(f"alias ID: {id(alias)}") # alias ID: 140735674123456

# 行为:修改其中一个,另一个也受影响
alias.append(4)
print(f"Original: {original}") # Output: Original: [1, 2, 3, 4]
print(f"Alias: {alias}") # Output: Alias: [1, 2, 3, 4]
深拷贝与浅拷贝的区别

当需要一个独立的对象副本时,就不能使用=,需要用到拷贝操作,即浅拷贝和深拷贝。

拷贝的问题只在处理“复合对象”时才重要,特别是当它包含“可变的子对象”时

sample

浅拷贝

浅拷贝会创建一个新的复合对象(例如列表、字典、集合等可变对象),对于原对象中包含的子对象(或称为嵌套对象),浅拷贝只会复制它们的引用,而不是创建这些子对象的独立副本,在处理嵌套对象时需要格外注意!

  • 对于不可变对象(int, float, str, bool, tuple, frozenset, bytes):浅拷贝和深拷贝的效果通常相同,因为不可变对象内容无法更改,所以也没有引用导致的值发生变化问题。
  • **对于可变对象(list, dict, set, bytearray)**:创建后,它的内容可以被修改。
  • 浅拷贝实现方式:
    • 使用 copy.copy(object) 函数
    • 对于列表,可以使用切片操作 [:]
    • 对于字典,可以使用 .copy() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
import copy

# 浅拷贝
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
shallow[0][0] = 99
print(original) # [[99, 2], [3, 4]]

# 深拷贝
deep = copy.deepcopy(original)
deep[0][0] = 88
print(original) # [[99, 2], [3, 4]]
print(deep) # [[88, 2], [3, 4]]

可视化结果

深拷贝

深拷贝会创建一个全新的复合对象,并且递归地复制原对象中所有嵌套的子对象,确保新对象和原对象之间完全独立,没有任何共享的引用。无论原对象及其子对象是可变还是不可变,修改新对象中的任何部分都不会影响原对象,反之亦然。

  • 必须使用 copy.deepcopy() 函数。

Python装饰器

Python 语言中,装饰器是“用一个函数装饰另外一个函数并为其提供额外的能力”的语法。装饰器本身是一个函数,它的参数是被装饰的函数,它的返回值是一个带有装饰功能的函数。装饰器是高阶函数,它的参数和返回值都是函数。

Python万物皆对象,也就是万物都可传参,函数也可以作为函数的参数进行传递。还要强调一下,Python 语言支持函数的嵌套定义,可以在一个函数中定义另一个函数,这个操作在很多编程语言中并不被支持。

装饰器给现有的模块增添新的小功能,可以对原函数进行功能扩展,不需要修改原函数的内容,也不需要修改原函数的调用情况

装饰器 在不修改函数本身的情况下,动态地增加或修改函数的行为。装饰器本质上也是一个函数,它接收一个函数作为参数输入,并在不改变原函数代码的情况下,返回一个增强的函数。核心思想是“函数是第一类对象”,即函数可以作为参数传递和返回。

装饰器要写在具体应用的函数前面,否则会报错;要先定义装饰器,才能在具体函数上面使用”@”装饰,在下面的示例中:

  • decorator 是装饰器函数,接受一个原函数 func 作为参数。
  • wrapper 是在装饰器内部定义的一个新函数,它在执行原始函数 func 之前和之后添加了自定义的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 由于要用wrapper替代原函数func,但是又不清楚原函数func会接受哪些参数,所以就通过可变参数和关键字参数照单全收,
# 然后在调用func的时候,原封不动的全部给它
def decorator(func):
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs)
print("After function call")
return result
return wrapper

# 两种使用装饰器的方式
# 第一种,直接调用装饰器函数
func = decorator(func)
# 第二种,语法糖
@decorator
func()

如果在代码的某些地方,想去掉装饰器的作用执行原函数,那么在定义装饰器函数的时候,需要做一点点额外的工作。Python 标准库functools模块的wraps函数也是一个装饰器,将它放在wrapper函数上,这个装饰器可以保留被装饰之前的函数,这样在需要取消装饰器时,可以通过被装饰函数的__wrapped__属性获得被装饰之前的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import random
import time

from functools import wraps


def record_time(func):

@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f'{func.__name__}执行时间: {end - start:.2f}秒')
return result

return wrapper


@record_time
def download(filename):
print(f'开始下载{filename}.')
time.sleep(random.random() * 6)
print(f'{filename}下载完成.')


@record_time
def upload(filename):
print(f'开始上传{filename}.')
time.sleep(random.random() * 8)
print(f'{filename}上传完成.')


# 调用装饰后的函数会记录执行时间
download('MySQL从删库到跑路.avi')
upload('Python从入门到住院.pdf')
# 取消装饰器的作用不记录执行时间
download.__wrapped__('MySQL必知必会.pdf')
upload.__wrapped__('Python从新手到大师.pdf')

带参数的装饰器

装饰器函数本身也是一个函数,既然是函数,就可以进行参数传递,如何写一个带参数的装饰器呢?

上例的装饰器只实现了计数功能,如果想在使用该装饰器的时候,传入一些备注的msg信息,应该怎么做?

基于原来的count_time函数外部再包一层用于接收参数的count_time_args,接收回来的参数就可以直接在内部的函数里面调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import time

def count_time_args(msg=None):
def count_time(func):
def wrapper(*args, **kwargs):
t1 = time.time()
func(*args, **kwargs)
print(f"[{msg}]执行时间为:", time.time() - t1)
return func
return wrapper
return count_time


@count_time_args(msg="baiyu")
def fun_one():
time.sleep(1)


@count_time_args(msg="zhh")
def fun_two():
time.sleep(1)


@count_time_args(msg="mylove")
def fun_three():
time.sleep(1)


if __name__ == '__main__':
fun_one()
fun_two()
fun_three()


# 输出
[baiyu]执行时间为: 1.0023813247680664
[zhh]执行时间为: 1.0086195468902588
[mylove]执行时间为: 1.004340410232544

类装饰器

类装饰器其实就是一个实现了 __call__() 方法的类,它可以像函数装饰器一样使用 @ 类名来修饰其他函数或类,使实例可调用,从而充当装饰器

🔹 类装饰器的本质

  1. 类被用作装饰器,必须实现 __call__() 方法。
  2. __call__() 方法会在被装饰函数调用时执行。
  3. 可以保持状态(相比于函数装饰器)。

类装饰器的实现是调用了类里面的__call__函数。类装饰器的写法比装饰器函数的写法更加简单。

当将类作为一个装饰器,工作流程如下:

  • 通过__init__()方法初始化类
  • 通过__call__()方法调用真正的装饰方法

在下面的示例中

  • CounterDecorator 的 __init__ 接收被装饰函数 say_hello(在构造函数中传入被装饰的函数)。

  • __call__ 使实例可调用,记录调用次数并执行原始函数。

  • @CounterDecorator 等价于 say_hello = CounterDecorator(say_hello)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CounterDecorator:
def __init__(self, func):
self.func = func # 保存被装饰函数
self.count = 0 # 记录调用次数

def __call__(self, *args, **kwargs):
self.count += 1
print(f"Function '{self.func.__name__}' has been called {self.count} time(s)")
return self.func(*args, **kwargs)

# 使用类装饰器
@CounterDecorator
def say_hello(name):
print(f"Hello, {name}!")

# 测试
say_hello("Alice")
say_hello("Bob")

# 输出结果
Function 'say_hello' has been called 1 time(s)
Hello, Alice!
Function 'say_hello' has been called 2 time(s)
Hello, Bob!

带参数的类装饰器

当装饰器包含参数的时候,就不能在__init__() 函数里传入func(要装饰的函数)了,而是将要装饰的函数func改为在__call__()函数调用的时候传入。

带参数的类装饰器允许向装饰器传递参数,如配置选项或自定义行为。实现方式通常是多层嵌套:外层类接受参数,内层类或方法处理被装饰函数,最后返回一个可调用对象。

在下面的示例中:

  • LogDecorator 的 __init__ 接受 prefix 和 max_calls 参数。

  • __call__ 接收被装饰函数 add,返回 wrapper 函数。

  • wrapper 检查调用次数,添加日志前缀并限制最大调用。

  • @LogDecorator(prefix=”DEBUG”, max_calls=3) 传递参数,创建装饰器实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class LogDecorator:
def __init__(self, prefix, max_calls=5):
self.prefix = prefix # 日志前缀
self.max_calls = max_calls # 最大调用次数
self.call_count = 0 # 当前调用次数

def __call__(self, func):
def wrapper(*args, **kwargs):
self.call_count += 1
if self.call_count > self.max_calls:
print(f"{self.prefix} Error: Maximum calls ({self.max_calls}) exceeded!")
return None
print(f"{self.prefix} Calling '{func.__name__}' (Call {self.call_count})")
return func(*args, **kwargs)
return wrapper

# 使用带参数的类装饰器
@LogDecorator(prefix="DEBUG", max_calls=3)
def add(a, b):
return a + b

# 测试
print(add(1, 2)) # Call 1
print(add(3, 4)) # Call 2
print(add(5, 6)) # Call 3
print(add(7, 8)) # Exceeds max_calls

装饰器的顺序

一个函数可以被多个装饰器进行装饰,那么装饰器的执行顺序是怎么样的?

由输出结果可知,在装饰器修饰完的函数,在执行的时候先执行原函数的功能,然后再由里到外(从下到上)依次执行装饰器的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def BaiyuDecorator_1(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器1')

return wrapper

def BaiyuDecorator_2(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器2')

return wrapper

def BaiyuDecorator_3(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
print('我是装饰器3')

return wrapper

@BaiyuDecorator_1
@BaiyuDecorator_2
@BaiyuDecorator_3
def baiyu():
print("我是攻城狮白玉")


if __name__ == '__main__':
baiyu()

运行结果

框架中的装饰器

在FastAPI框架中的 @app.router() 装饰器,它们和 Python 中常见的装饰器一样,本质上通过 @ 符号修饰目标函数,但是它们的上下文通常与特定的框架有关,用于实现框架内部的功能(如 HTTP 路由);

一些框架(如 FlaskFastAPI 等)也使用了类似的装饰器语法来定义路由。这里的 @app.router() 也是一个装饰器,它的作用是将一个函数绑定到特定的 HTTP 路由上。当访问该路由时,框架会调用装饰器绑定的函数。

1
2
3
4
5
6
7
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"message": "Hello, World!"}

总结

经典的Python装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#  计时装饰器,可以用来测量函数执行时间。
import time

def timing_decorator(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} took {end - start} seconds to execute")
return result
return wrapper

@timing_decorator
def example_function():
time.sleep(1)


example_function()


# 日志记录装饰器,用于在函数调用前后打印日志信息。
def logging_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} finished executing.")
return result
return wrapper

@logging_decorator
def another_example():
pass

another_example()

# 类装饰器
def add_extra_attribute(cls):
cls.extra_attribute = "This is an extra attribute."
return cls


@add_extra_attribute
class MyClass:
pass


instance = MyClass()
print(instance.extra_attribute)

类方法&静态方法&抽象方法

类方法

类方法是属于类的方法,而不是类的某个实例。它接收的第一个参数通常是类本身,而不是实例。在Python中,使用@classmethod装饰器来定义类方法。

使用类方法来实例化对象,作为 __init__() 的替代或补充,是一种非常常见且推荐的模式。这种模式被称为工厂方法(Factory Method)。类方法充当了一个方便的“工厂”,它接收一个参数输入,然后在内部进行必要的处理、转换或验证,最终调用 类本身的__init__ 来完成实际的实例创建。这是 Python 中一种非常优雅和实用的设计模式,尤其在处理复杂对象创建逻辑时,它能大大提升代码的清晰度、可读性和可维护性。

特点

  • 第一个参数是类本身:惯例上,第一个参数名为cls(Class的缩写),代表调用该方法的类,在调用类方法时不需要在参数列表上加cls,Python解释器会自动添加,就像类方法不需要自己添加self一样。

  • 可以通过类或实例调用:无论是通过ClassName.method()还是instance.method()cls参数都指向ClassName本身。

  • 主要用途:常用于创建工厂方法(Factory Methods),即根据不同的参数创建类的实例。还可以用于定义一些与类相关但不需要实例状态的操作。提供了一种替代构造函数的方式,可以添加自定义逻辑(例如验证 data 或转换格式)。

假设有一个Circle类,可以使用类方法来创建一个以特定半径或直径初始化的圆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Circle:
pi = 3.14159

def __init__(self, radius):
self.radius = radius

@classmethod
def from_diameter(cls, diameter):
# cls represent the Circle class
radius = diameter / 2
return cls(radius) # Create a new Circle instance

def area(self):
return self.pi * (self.radius ** 2)

# 使用类方法创建实例
c1 = Circle(5) # radius = 5
c2 = Circle.from_diameter(10) # diameter = 10, radius = 5

print(c1.area()) # 78.53975
print(c2.area()) # 78.53975

具体应用场景

类方法最适合用作工厂方法 (Factory Methods)。当一个类有多种不同的方式来创建实例时,类方法能让代码更清晰,也更符合面向对象的设计原则。

假设正在开发一个用户管理系统,User 类是核心。用户数据可能来自不同的地方,比如字典(通常是JSON或数据库查询结果)或者一个带分隔符的字符串(比如从CSV文件读取)。可以使用类方法来处理这些不同的数据来源。

如果用普通方法实现,可能需要编写像create_user_from_dict()这样的独立函数,或者在__init__中用复杂的条件判断来处理不同类型的数据。使用类方法则将这些创建逻辑清晰地组织在User类内部,使得代码更易读、更符合单一职责原则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class User:
def __init__(self, user_id, name, email):
self.user_id = user_id
self.name = name
self.email = email

def __repr__(self):
return f"User(id={self.user_id}, name='{self.name}', email='{self.email}')"

@classmethod
def from_dict(cls, data):
"""
工厂方法:从字典创建User实例
"""
# cls在这里就是User类本身
return cls(data['id'], data['name'], data['email'])

@classmethod
def from_string(cls, data_string):
"""
工厂方法:从带分隔符的字符串创建User实例
"""
user_id, name, email = data_string.split('|')
return cls(int(user_id), name, email)

# 场景一:从字典创建用户
user_data_dict = {'id': 101, 'name': 'Alice', 'email': 'alice@example.com'}
user1 = User.from_dict(user_data_dict)
print(user1)
# 输出:User(id=101, name='Alice', email='alice@example.com')

# 场景二:从字符串创建用户
user_data_string = '102|Bob|bob@example.com'
user2 = User.from_string(user_data_string)
print(user2)
# 输出:User(id=102, name='Bob', email='bob@example.com')

静态方法

静态方法是一种与类相关联,但不依赖于类或实例状态的方法。它不接收clsself作为第一个参数。在Python中,使用@staticmethod装饰器来定义静态方法。

特点

  • 没有默认参数:不需要接收selfcls,它的行为就像一个普通的函数,只是它被归属于类的命名空间

  • 可以通过类或实例调用:调用方式与类方法类似,但它无法访问类或实例的属性或方法

  • 主要用途:用于存放与类逻辑相关但又不依赖于任何类或实例数据的工具函数。这使得代码结构更清晰,将相关功能组织在一起。

可以为Circle类添加一个静态方法来计算两个数的平均值,这个函数与Circle本身无关,但我们可以将其归入Circle的命名空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Circle:
pi = 3.14159

def __init__(self, radius):
self.radius = radius

@staticmethod
def calculate_average(a, b):
return (a + b) / 2

# 使用类调用静态方法
avg = Circle.calculate_average(10, 20)
print(avg) # 15.0

具体应用场景

假设正在开发一个密码管理工具,PasswordManager类负责存储和验证密码。需要一个函数来检查密码的强度。这个检查逻辑独立于任何具体的PasswordManager实例,它只关心输入的字符串本身。

如果将is_strong_password作为一个独立的函数放在类的外部,代码结构会显得分散。将其作为静态方法,可以清楚地表明这个工具函数是专为PasswordManager类服务的,增强代码的内聚性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class PasswordManager:
# 这里有存储和管理密码的逻辑...

@staticmethod
def is_strong_password(password):
"""
静态方法:检查密码是否符合强度要求
这个方法不依赖于PasswordManager的任何实例或类属性
"""
# 实际的密码强度检查会更复杂
if len(password) < 8:
return False
if not any(char.isdigit() for char in password):
return False
if not any(char.isupper() for char in password):
return False
return True

# 检查一个密码的强度,无需创建PasswordManager实例
password_to_check = "MySecureP@ss123"
if PasswordManager.is_strong_password(password_to_check):
print("密码强度符合要求。")
else:
print("密码强度不足。")

# 输出:密码强度符合要求。

抽象方法

抽象方法是一个声明了但没有具体实现的方法。它必须在抽象基类(Abstract Base Class,简称ABC)中定义。子类继承这个抽象基类后,必须实现这个抽象方法,否则子类无法被实例化。在Python中,使用abc模块的ABC类和@abstractmethod装饰器来实现

特点

  • 必须在子类中实现:抽象方法强制子类提供特定的实现,这有助于定义一个公共接口,其实就是相当于java中的interface 接口关键字。

  • 强制实现:如果子类没有实现所有的抽象方法,那么创建子类的实例会引发TypeError

  • 主要用途:用于创建接口(Interface)或模板。它定义了一个类的蓝图,强制所有继承它的子类遵循相同的结构,方便后续进一步开发与规范。

假设正在构建一个图形绘制程序,可以创建一个Shape抽象基类,并定义一个抽象方法draw()。所有具体的图形类(如CircleSquare)都必须实现draw()方法,才能进行后续的类实例化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from abc import ABC, abstractmethod

class Shape(ABC):
@abstractmethod
def draw(self):
pass # No implementation here

class Circle(Shape):
def draw(self):
print("Drawing a circle.")

class Square(Shape):
def draw(self):
print("Drawing a square.")

# 直接实例化抽象类会失败
# s = Shape() # TypeError: Can't instantiate abstract class Shape with abstract method draw

# 正确的实例化方式
circle = Circle()
square = Square()

circle.draw() # Drawing a circle.
square.draw() # Drawing a square.

具体应用场景

抽象方法用于定义一个公共的接口。它强制所有继承自抽象基类的子类必须实现该方法,从而确保不同子类都具有相同的行为。

假设正在开发一个文件处理器系统,需要处理不同类型的文件,比如PDFWordText等。虽然每种文件的处理方式不同,但它们都必须有一个read方法来读取内容和一个save方法来保存。抽象方法可以定义这个通用的处理接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from abc import ABC, abstractmethod

class FileProcessor(ABC):
"""
抽象基类,定义文件处理的公共接口
"""
def __init__(self, filepath):
self.filepath = filepath

@abstractmethod
def read_content(self):
"""
抽象方法:子类必须实现如何读取文件内容
"""
pass

@abstractmethod
def save_content(self, content):
"""
抽象方法:子类必须实现如何保存文件内容
"""
pass

class PDFProcessor(FileProcessor):
def read_content(self):
print(f"正在读取PDF文件:{self.filepath}")
# 实际代码会使用PDF解析库
return "PDF文件内容..."

def save_content(self, content):
print(f"正在保存PDF文件:{self.filepath}")
# 实际代码会使用PDF生成库
pass

class TextProcessor(FileProcessor):
def read_content(self):
print(f"正在读取TXT文件:{self.filepath}")
# 实际代码会读取文本文件
return "TXT文件内容..."

def save_content(self, content):
print(f"正在保存TXT文件:{self.filepath}")
# 实际代码会写入文本文件
pass

# 使用抽象类定义的接口来处理不同文件
pdf_file = PDFProcessor("document.pdf")
text_file = TextProcessor("note.txt")

pdf_content = pdf_file.read_content()
text_content = text_file.read_content()

pdf_file.save_content("新的PDF内容")
text_file.save_content("新的TXT内容")

总结


特性 类方法 (Class Method) 静态方法 (Static Method) 抽象方法 (Abstract Method)
装饰器 @classmethod @staticmethod @abstractmethod
第一个参数 cls (类本身) 无(但在子类中实现时通常包含selfcls
访问能力 能够访问类的属性和方法 不能访问类或实例的属性和方法 强制子类实现,行为由子类决定
主要用途 工厂方法,处理与类相关但不需要实例状态的操作,替代或扩展构造函数 组织与类逻辑相关但不依赖于任何类或实例数据的工具函数 定义接口或模板,强制子类遵循特定结构
调用方式 ClassName.method()instance.method() ClassName.method()instance.method() 在子类中实现后通过instance.method()调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用类方法替代构造方法的示例
class MyClass:
@classmethod
def from_data(cls, data):
return cls(data)

def __init__(self, data):
self.data = data
print(self.data)

# 使用类方法新建类实例
obj = MyClass.from_data("test")
print(obj.data) # 输出: test

# 使用构造方法新建类实例
obj_2 = MyClass('test2')

lambda函数

lambda 函数也称为 匿名函数,使用 lambda 关键字定义。主要包含以下特点:

  • 匿名性: 没有函数名,无需 def 和函数名定义。

  • 简洁性: 只能包含一个表达式,这个表达式的执行结果就是函数的返回值(important!!!)

  • 即时性: 通常用于一次性使用的简单操作,无需定义一个完整的函数。

Lambda 函数的语法

lambda 参数1, 参数2, ... : 表达式

  • lambda: 关键字,表示这是一个 lambda 函数。

  • arguments: 函数的参数列表,可以有0个或多个参数,使用逗号进行分隔。

  • expression: lambda 函数的“函数体”。它必须是一个单一的表达式,其计算结果就是 lambda 函数的返回值。

1
2
3
4
5
6
7
8
# 无参数的 lambda 表达式
greet = lambda: print("Hello, World!")

# 调用 lambda 函数
greet()

# 输出
Hello, World!

lambda 函数常见使用场景

作为高阶函数的参数

常用于 map()filter()sorted() 等函数式编程场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. 基本数学运算
add = lambda x, y: x + y
print(add(2, 3)) # 输出 5

# 2. 结合 map() 处理列表
numbers = [1, 2, 3, 4]
squares = list(map(lambda x: x**2, numbers))
print(squares) # 输出 [1, 4, 9, 16]

# 3. 结合 filter() 筛选数据,lambda的返回值是True or False,filter() 会保留返回为 True 的元素
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # 输出 [2, 4]

# 4. 结合 sorted() 自定义排序,按元组的第二个元素(字符串)排序,排序是基于字符串的 ASCII 码按字典序升序进行的。
pairs = [(1, 'one'), (3, 'three'), (2, 'two')]
sorted_pairs = sorted(pairs, key=lambda x: x[1])
print(sorted_pairs) # 输出 [(1, 'one'), (3, 'three'), (2, 'two')]

# 5. 条件表达式
is_positive = lambda x: "Positive" if x > 0 else "Non-positive"
print(is_positive(5)) # 输出 Positive
print(is_positive(-1)) # 输出 Non-positive

# 6. 嵌套 Lambda(函数组合)
compose = lambda x: (lambda y: x + y)(10)
print(compose(5)) # 输出 15

lambda 函数可以返回一个元组,这样它看起来就好像返回了多个值

lambda 函数永远只有一个返回值。这是它设计上的一个核心限制。任何一个表达式求值的结果都只会是一个值。如果一个lambda函数有“多个返回值”时,它实际上并非返回了多个独立的值,而是返回了一个**元组 (tuple)**。这个元组包含了返回的所有“值”。

1
2
3
4
5
6
7
8
9
10
11
12
13
calculate_power_lambda = lambda x: (x * x, x * x * x)

# 调用 lambda 函数,并将返回的元组解包
result_square_l, result_cube_l = calculate_power_lambda(4)
print(f"Lambda 函数平方: {result_square_l}, 立方: {result_cube_l}")
# 输出: Lambda 函数平方: 16, 立方: 64

# 或者不解包,直接看返回值
single_return_value_from_lambda = calculate_power_lambda(4)
print(f"Lambda 函数的实际返回值类型: {type(single_return_value_from_lambda)}")
print(f"Lambda 函数的实际返回值: {single_return_value_from_lambda}")
# 输出: Lambda 函数的实际返回值类型: <class 'tuple'>
# 输出: Lambda 函数的实际返回值: (16, 64)

Lambda 的表达式也可以是一个函数。Lambda 函数的表达式可以调用任何其他函数,并返回被调用函数的返回值作为lambda的返回值!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def multiply_by_ten(num):
return num * 10

my_lambda = lambda x: multiply_by_ten(x)

result = my_lambda(5)
print(f"Lambda 调用外部函数的结果: {result}") # 输出: Lambda 调用外部函数的结果: 50
----------------------------------------------------------------------------------------------------------------------------------------------------------------

# case 2
numbers = [1, 2, 3]

def add_one(n):
return n + 1

# lambda 表达式中调用 add_one
processed_numbers = list(map(lambda x: add_one(x), numbers))
print(f"map 结合 lambda 和函数调用: {processed_numbers}") # 输出: map 结合 lambda 和函数调用: [2, 3, 4]

Python解包

解包是Python中一项强大而实用的特性,它允许将容器类型(如列表、元组、字典等)中的元素拆解为独立变量或参数,从而简化代码并提高可读性。将可迭代对象(列表、元组、字典等)的元素直接赋值给多个变量,或在函数调用、循环等场景中展开元素。

  • * 在解包中只能使用一次。

  • ** 仅适用于字典,且键名需与函数参数匹配。

基本语法:variable1, variable2, … = iterable

封包

封包指的是,将多个值赋值给一个变量时,Python 会自动将这些值封包为一个元组。

1
2
3
>>> count = 996, 123, -3
>>> count
(996, 123, -3)

基础解包

将可迭代对象(列表、元组、字符串等)中的元素按顺序分配给多个变量。

  • 变量数量必须与元素数量一致,否则会报错ValueError
  • 支持所有可迭代对象(列表、元组、字符串、集合等)。不可迭代对象解包会报错a, b = 100
  • 集合是无序结构,解包时变量接收元素的顺序可能与定义时不同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 列表解包
colors = ['red', 'blue', 'green']
red, blue, green = colors
print(red) # 'red'
print(blue) # 'blue'

# 元组解包
x, y = (10, 20)
print(x) # 10
print(y) # 20

# 字符串解包
a, b, c = "abc"
print(b) # 'b'

# 集合解包(顺序不确定)
s = {1, 2, 3}
a, b, c = s
print(a, b, c) # 可能输出 1 2 3 或 3 1 2 等

扩展解包

使用*操作符捕获多余元素,处理不定长序列。* 在解包中只能使用一次。

  • *变量始终返回列表,即使没有元素。
  • *可以放在任意位置,收集剩余元素。
  • 可以忽略不需要的元素:a, *_, c = (1, 2, 3)
  • 支持多层嵌套解包:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 捕获中间元素
first, *middle, last = [1, 2, 3, 4, 5]
print(middle) # [2, 3, 4]

# 处理任意长度序列
a, *b = [10]
print(a) # 10
print(b) # [] # 空列表

# case 2
data = [
1, # 第一层元素1
[2, 3, 4], # 第二层列表
(5, 6) # 第二层元组
]
a, (b, *c), d = data
# 解包第一层:a=1
# 解包第二层列表得 b=2,*c收集剩余元素[3,4]
# d实际接收元组(5,6)
print(a)
print(b)
print(c) # [3, 4]
print(d) # (5, 6) # 元组未进一步解包,d仍是元组类型


# 输出
1
2
[3, 4]
(5, 6)
1
2
3
4
5
6
7
8
# 使用*_忽略多余元素
data = [1, 2, 3, 4, 5]
a, b, *_ = data # 只取前两个,忽略其余

# 默认值处理
values = [10, 20]
x, y, *rest = values
z = rest[0] if rest else None

字典解包

字典解包默认解包的是键(key),不是值(value)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 字典键解包
person = {'name': '张三', 'age': 25}
key1, key2 = person
print(key1) # 'name'
print(key2) # 'age'

# 字典值解包(values()方法)
person = {'name': '张三', 'age': 25}
value1, value2 = person.values()
print(value1, value2) # '张三' 25

# 字典键值对解包(items()方法)
for key, value in person.items():
print(f"{key}: {value}")

循环中的解包

直接解包迭代器中的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 遍历元组列表
points = [(1, 2), (3, 4), (5, 6)]
for x, y in points:
print(f"坐标:({x}, {y})")

# 遍历字典项
user_info = {"name": "小帅", "age": 30}
for key, value in user_info.items():
print(f"{key}: {value}")

# 输出
坐标:(1, 2)
坐标:(3, 4)
坐标:(5, 6)
name: 小帅
age: 30

应用场景

数据合并

1
2
3
4
5
6
7
8
9
# 合并多个列表
list1 = [1, 2]
list2 = [3, 4]
combined = [*list1, *list2] # [1, 2, 3, 4]

# 合并多个字典,后出现的键(dict2的z)覆盖前一个
dict1 = {'x': 1, 'z': 3}
dict2 = {'y': 2, 'z': 4}
merged = {**dict1, **dict2} # {'x': 1, 'z': 4, 'y': 2}

提取数据

1
2
3
4
# 提取首尾元素
numbers = [10, 20, 30, 40]
first, *_, last = numbers
print(first, last) # 10 40

Python高阶函数

高阶函数是指满足以下任意一个或同时满足两个条件的函数

  1. 接受一个或多个函数作为参数(最常见)
  2. 返回一个函数作为结果

这使得函数可以像普通参数(如整数、字符串)一样被传递、赋值和操作。高阶函数(如 map()filter())适合大数据处理,但有些高阶函数的返回值可能是一个迭代器,需要用户自行显式转换为列表或元组,才能直接print出来,也可以使用next()一个个迭代来加载元素。

  • map(function, iterable, ...):对可迭代对象的每个元素应用函数function,返回迭代器。
  • filter(function, iterable):筛选可迭代对象中满足函数function条件的元素,返回迭代器。
  • sorted(iterable, key=None, reverse=False):按 key 函数排序,返回一个新的列表。
  • reduce(function, iterable[, initializer]):从 functools 模块,累计应用函数到可迭代对象。

map() 函数

功能: 对可迭代对象中的每个元素应用同一个函数,并返回一个迭代器。

1
2
3
4
5
6
7
8
9
10
11
12
numbers = [1, 2, 3, 4, 5]

def square(x):
return x * x

# map 接受 square 函数和 numbers 列表作为参数,对numbers中的每一个元素执行square()操作
squared_numbers = map(square, numbers)
print(f"map() 使用常规函数: {list(squared_numbers)}") # 输出: map() 使用常规函数: [1, 4, 9, 16, 25]

# 常用结合 lambda 函数
squared_numbers_lambda = map(lambda x: x * x, numbers)
print(f"map() 使用 lambda 函数: {list(squared_numbers_lambda)}") # 输出: map() 使用 lambda 函数: [1, 4, 9, 16, 25]

filter() 函数

功能: 根据一个返回布尔值的函数来过滤可迭代对象中的元素,只保留使函数返回为 True 的元素。

1
2
3
4
5
6
7
8
9
10
11
12
numbers = [1, 2, 3, 4, 5, 6]

def is_even(x):
return x % 2 == 0

# filter 接受 is_even 函数和 numbers 列表作为参数
even_numbers = filter(is_even, numbers)
print(f"filter() 使用常规函数: {list(even_numbers)}") # 输出: filter() 使用常规函数: [2, 4, 6]

# 常用结合 lambda 函数
even_numbers_lambda = filter(lambda x: x % 2 == 0, numbers)
print(f"filter() 使用 lambda 函数: {list(even_numbers_lambda)}") # 输出: filter() 使用 lambda 函数: [2, 4, 6]

sorted() 函数

功能: 对可迭代对象进行排序,并可选地接受一个 key 参数,该参数是一个函数,用于生成可迭代对象中用于比较的键值。

1
2
3
4
5
students = [('Alice', 20), ('Bob', 25), ('Charlie', 18)]

# sorted 接受 lambda 函数作为 key 参数
sorted_by_age = sorted(students, key=lambda student: student[1])
print(f"sorted() 使用 lambda 作为 key: {sorted_by_age}") # 输出: sorted() 使用 lambda 作为 key: [('Charlie', 18), ('Alice', 20), ('Bob', 25)]

functools.reduce() 函数 (需要导入)

功能: 对可迭代对象中的元素进行累积操作(例如求和、求积),从左到右依次将元素和上一次操作的结果传入一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from functools import reduce

numbers = [1, 2, 3, 4]

# 定义一个函数来计算乘积
def multiply(x, y):
return x * y

# reduce 接受 multiply 函数和 numbers 列表作为参数
product = reduce(multiply, numbers)
print(f"reduce() 使用常规函数: {product}") # 输出: reduce() 使用常规函数: 24 (1*2*3*4)

# 常用结合 lambda 函数
product_lambda = reduce(lambda x, y: x * y, numbers)
print(f"reduce() 使用 lambda 函数: {product_lambda}") # 输出: reduce() 使用 lambda 函数: 24

可迭代对象&迭代器&生成器

迭代:通过for循环遍历对象的每一个元素的过程,字符串、列表、字典、元组、集合等都是可以迭代数据类型,也是可迭代对象。

为什么有可迭代对象还不够,还需要迭代器?

总结:可迭代对象适合静态、小数据集,但内存占用高,灵活性有限。迭代器通过惰性求值和自定义逻辑,优化内存和性能,满足大数据、流处理和复杂遍历需求。

迭代器(Iterator)在 Python 中用于逐个访问可迭代对象的元素,提供以下关键用途:

  1. 内存效率:迭代器一次只生成一个元素,适合处理大数据(如大文件或流式数据),不像列表将所有元素一次性加载到内存。
  2. 惰性求值:按需生成数据,延迟计算,优化性能。
  3. 单向遍历:支持一次遍历,适合单次处理场景(如日志分析)。
  4. 自定义迭代:允许定义特定遍历逻辑(如跳跃、过滤)。
  5. 流式处理:与生成器结合,处理实时或无限数据流。

为什么仅有可迭代对象不够?

可迭代对象(如列表、元组)可以通过 iter() 返回迭代器,但自身功能有限:

  1. 内存占用:
    • 可迭代对象(如列表)一次性存储所有数据,内存开销大。
    • 迭代器逐个生成数据,适合大文件或动态数据。
  2. 灵活性不足:
    • 可迭代对象提供固定遍历顺序,无法动态调整。
    • 迭代器通过 next() 可自定义遍历逻辑(如跳跃、条件过滤)。
  3. 一次性遍历:
    • 可迭代对象可多次迭代,迭代器一次耗尽,适合临时数据处理。
  4. 动态生成:
    • 可迭代对象通常是静态数据,迭代器(尤其是生成器)支持动态生成数据,如实时计算或流处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 可迭代对象:列表全加载
lst = [x for x in range(1000000)] # 占用大量内存
for x in lst: # 多次迭代
if x > 5: break
print(lst[0]) # 可重复访问

# 迭代器:惰性生成
class SkipIterator:
def __init__(self, data):
self.data = data
self.index = 0

def __iter__(self):
return self

def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 2 # 自定义:跳跃遍历
return value

it = SkipIterator([1, 2, 3, 4, 5])
for x in it: # 仅遍历 1, 3, 5
print(x) # 输出 1 3 5

在 Python 中,__iter__ 和 __next__ 是与迭代概念相关的魔术方法,用于实现可迭代对象迭代器

  1. __iter__ 方法
  • 作用:使对象成为可迭代对象,返回一个迭代器。
  • 定义:在类中定义 __iter__(self),返回一个实现了 __next__ 方法的迭代器对象(通常是 self)。
  • 调用时机:当对象用于 for 循环或 iter() 函数时,Python 调用 __iter__。
  1. __next__ 方法
  • 作用:定义迭代器的行为,返回下一个元素。
  • 定义:在类中定义 __next__(self),每次调用返回迭代的下一个值,迭代结束时抛出 StopIteration 异常。
  • 调用时机:在迭代过程中,next() 函数或 for 循环会调用 __next__

迭代器 (Iterator) 和 for ... in ... 循环

在 Python 中,for ... in ... 循环能够遍历任何**可迭代对象 (Iterable)**。当 for 循环遇到一个可迭代对象时,它会做以下几件事:

  1. 获取迭代器: 它会调用可迭代对象的 __iter__() 方法,从而获取一个迭代器 (Iterator) 对象。
  2. 反复调用 next() 循环会反复地调用这个迭代器的 __next__() 方法来获取下一个元素。
  3. 捕获 StopIteration 当迭代器没有更多元素时,它会抛出一个 StopIteration 异常。for 循环会捕获这个异常,并优雅地结束循环。

zip() 函数的返回值:一个迭代器

1
2
3
4
5
6
7
8
9
products = ['Apple', 'Banana', 'Orange']
prices = [1.0, 0.5, 1.2]

for product, price in zip(products, prices):
print(f"{product} 的价格是 ${price}")
# 输出:
# Apple 的价格是 $1.0
# Banana 的价格是 $0.5
# Orange 的价格是 $1.2

我们知道 zip(products, prices) 的返回值是一个 zip 迭代器。这个迭代器在被创建时,它并不会立即生成所有配对的元组,而是按需生成

zip(products, prices) 的执行:

  • for 循环开始时,它首先调用 zip(products, prices)
  • zip() 函数会创建一个 zip 对象(这是一个迭代器)。这个 zip 对象内部知道它需要从 products 列表和 prices 列表中分别取元素。
  • 此刻,还没有任何元组被实际创建!

循环的第一次迭代:

  • for 循环会要求 zip 迭代器给出第一个元素。
  • zip 迭代器会从 products 中取出第一个元素 'Apple',从 prices 中取出第一个元素 1.0
  • 它将这两个元素打包成一个元组:('Apple', 1.0)
  • 然后,这个元组 ('Apple', 1.0) 被返回给 for 循环。

元组解包 (Tuple Unpacking):

  • 紧接着,for product, price in ... 这里的 product, price 发生了元组解包
  • Python 将 ('Apple', 1.0) 这个元组解包,把 'Apple' 赋值给变量 product,把 1.0 赋值给变量 price
  • 现在,在循环体的第一次执行中,product 的值是 'Apple'price 的值是 1.0
  • print(f"{product} 的价格是 ${price}") 就会输出 “Apple 的价格是 $1.0”。

循环的第二次迭代:

  • for 循环再次要求 zip 迭代器给出下一个元素。
  • zip 迭代器会从 products 中取出第二个元素 'Banana',从 prices 中取出第二个元素 0.5
  • 它将这两个元素打包成一个元组:('Banana', 0.5)
  • 这个元组被返回给 for 循环,然后被解包:product 变为 'Banana'price 变为 0.5
  • 循环体执行,输出 “Banana 的价格是 $0.5”。

循环的第三次迭代:

  • 过程同上。zip 返回 ('Orange', 1.2)
  • 解包后,product 变为 'Orange'price 变为 1.2
  • 循环体执行,输出 “Orange 的价格是 $1.2”。

循环结束:

  • for 循环再次要求 zip 迭代器给出下一个元素。
  • 此时,productsprices 都已经没有更多元素了。
  • zip 迭代器内部判断无法再生成新的配对元组,于是它会抛出 StopIteration 异常。
  • for 循环捕获这个异常,并知道循环已经完成,于是退出。

这种机制的优点是内存效率高。特别是当你的列表非常大时,zip() 不会一次性在内存中创建所有配对的元组,而是按需一个一个地生成,这在处理大数据集时尤为重要。

函数返回值

Python 函数可以返回任何类型的数据,包括但不限于:

  • 单一值: 最常见的情况,比如一个整数、一个字符串、一个布尔值、一个浮点数等。

  • 元组 (Tuple): 当你需要返回多个独立的值时,元组是一种非常常见且推荐的方式。

  • 列表 (List): 如果返回的是一个可变的序列,列表会更合适。

  • 字典 (Dictionary): 当你需要返回一组键值对数据时。

  • 集合 (Set): 如果返回的是一个无序不重复的元素集合。

  • None: 如果函数没有明确的 return 语句,或者 return 后面没有任何值,函数会隐式地返回 None

  • 自定义对象: 可以返回你自己定义的类的实例对象。

  • 其他函数: Python 函数甚至可以返回另一个函数(高阶函数)。

Python 在返回多个值时,实际上会默认将这些值封装成一个元组。在这个例子中,虽然写的是 return name, age, city,看起来像是返回了三个独立的值,但 Python 内部会将它们自动打包成一个元组 ('张三', 30, '北京')。当用一个变量 info 来接收时,info 就成了一个元组。这种行为在 Python 中被称为**元组打包 (Tuple Packing)**。

如果只返回一个值,它就是那个值本身的类型:如return "李四"return a + b

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def get_user_info():
name = "张三"
age = 30
city = "北京"
return name, age, city # 看起来返回了三个值

# 调用函数并接收返回值
info = get_user_info()
print(info)
print(type(info))

输出:
('张三', 30, '北京')
<class 'tuple'>

面向对象编程

面向对象编程:把一组数据和处理数据的方法组成对象,把行为相同的对象归纳为,通过封装隐藏对象的内部细节,通过继承实现类的特化和泛化,通过多态实现基于对象类型的动态分派。

在面向对象编程的世界中,一切皆为对象对象都有属性和行为每个对象都是独一无二的,而且对象一定属于某个类。对象的属性是对象的静态特征,对象的行为是对象的动态特征。按照上面的说法,如果把拥有共同特征的对象的属性和行为都抽取出来,就可以定义出一个类。

在很多场景下,面向对象编程其实就是一个三步走的问题。第一步定义类,第二步创建对象,第三步给对象发消息。有的时候是不需要第一步的,因为我们想用的类可能已经存在了,找到导入并调用就行。Python内置的listsetdict其实都是类,但是创建列表、集合、字典对象时,无需自定义类。当然,有的类并不是 Python 标准库中直接提供的,它可能来自于第三方的代码,在某些特殊的场景中,可能会用到名为“内置对象”的对象,所谓“内置对象”就是说上面三步走的第一步(定义类)和第二步(创建对象)都不需要了,因为类已经存在而且对象已然创建过了,直接向对象发消息就可以了,这也就是常说的“开箱即用”。

写在类里面的函数通常称之为方法,方法就是对象的行为,也就是对象可以接收的消息。方法的第一个参数通常都是self,它代表了接收这个消息的对象本身。在Python中,类的实例化对象调用方法有两种方式。第一种方法可以帮助理解self的含义

1
2
3
4
5
6
7
8
9
10
11
# 通过“类.方法”调用方法
# 第一个参数是接收消息的对象
# 第二个参数是学习的课程名称
Student.study(stu1, 'Python程序设计') # 学生正在学习Python程序设计.
# 通过“对象.方法”调用方法
# 点前面的对象就是接收消息的对象
# 只需要传入第二个参数课程名称
stu1.study('Python程序设计') # 学生正在学习Python程序设计.

Student.play(stu2) # 学生正在玩游戏.
stu2.play() # 学生正在玩游戏.

常用函数

enumerate()

enumerate() 是 Python 中的一个内置函数,允许你在遍历序列(如列表、元组、字符串等)的同时,轻松地获取每个元素的索引及其。这在很多场景下都比单独维护一个计数器要更加简洁。

  • enumerate() 用法:接受一个可迭代对象作为参数,并返回一个可迭代的 enumerate 对象。这个 enumerate 对象在每次迭代时会产生一个包含两个元素的元组:(index, value)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 语法
enumerate(iterable, start=0)
iterable: 必需参数,任何支持迭代的对象(例如列表、元组、字符串、字典、集合)。
start: 可选参数,指定索引的起始值,默认为 0,并不是指可迭代对象的下标,而是开始计数的值!!
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
# 使用示例
# 1. 遍历列表并获取索引和值
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(f"Index: {index}, Fruit: {fruit}")
输出:
Index: 0, Fruit: apple
Index: 1, Fruit: banana
Index: 2, Fruit: cherry
--------------------------------------------------------------------------------

# 2. 从指定索引开始计数
colors = ["red", "green", "blue"]
for i, color in enumerate(colors, start=5):
print(f"Number: {i}, Color: {color}")
输出:
Number: 5, Color: red
Number: 6, Color: green
Number: 7, Color: blue
--------------------------------------------------------------------------------

# 3. 遍历字符串
word = "Python"
for idx, char in enumerate(word):
print(f"Position: {idx}, Character: {char}")
输出:
Position: 0, Character: P
Position: 1, Character: y
Position: 2, Character: t
Position: 3, Character: h
Position: 4, Character: o
Position: 5, Character: n
--------------------------------------------------------------------------------

# 4. 在字典中使用 (通常是遍历键,然后通过键获取值)
# 虽然可以直接遍历字典的键值对,但如果需要在遍历键时额外获取索引,enumerate 也很方便
my_dict = {"a": 1, "b": 2, "c": 3}
for i, key in enumerate(my_dict):
print(f"Index: {i}, Key: {key}, Value: {my_dict[key]}")
输出:
Index: 0, Key: a, Value: 1
Index: 1, Key: b, 雅安: 2
Index: 2, Key: c, Value: 3
--------------------------------------------------------------------------------

# 5. 使用 enumerate() 的结果直接转换为列表或元组
fruits = ["apple", "banana", "cherry"]
colors = ["red", "green", "blue"]
enumerated_list = list(enumerate(fruits))
print(f"List from enumerate: {enumerated_list}")

enumerated_tuple = tuple(enumerate(colors, start=10))
print(f"Tuple from enumerate: {enumerated_tuple}")
输出:
List from enumerate: [(0, 'apple'), (1, 'banana'), (2, 'cherry')]
Tuple from enumerate: ((10, 'red'), (11, 'green'), (12, 'blue'))

sort() # 与高阶函数sorted()不同

sort() 是 Python 中列表对象的一个方法,它的主要功能是对列表中的元素进行原地排序。这意味着它会直接修改原始列表,而不是创建一个新的已排序列表。

  • 原地排序 (In-place Sorting): 这是 sort() 最重要的特性。它不会返回一个新的排序后的列表,而是直接修改调用它的列表。因此,如果需要保留原始列表的顺序,应该先复制一份列表再进行排序,或者使用 sorted()函数。
1
2
3
4
5
6
7
8
9
my_list = [3, 1, 4, 1, 5, 9, 2]
my_list.sort() # 直接修改 my_list
print(my_list) # 输出: [1, 1, 2, 3, 4, 5, 9]

original_list = [5, 2, 8]
sorted_list = original_list.copy() # 先复制一份
sorted_list.sort()
print(original_list) # 输出: [5, 2, 8] (原始列表未变)
print(sorted_list) # 输出: [2, 5, 8]
  • 默认升序排序: 如果没有指定其他参数,sort() 默认会按照元素的自然顺序进行升序排列。

    • 数字:从小到大

    • 字符串:按字母顺序(ASCII 值)

1
2
3
4
5
6
7
8
9
10
11
12
numbers = [5, 2, 8, 1, 9]
numbers.sort()
print(numbers) # 输出: [1, 2, 5, 8, 9]

words = ["banana", "apple", "cherry"]
words.sort()
print(words) # 输出: ['apple', 'banana', 'cherry']

# 可以通过设置 reverse=True 参数来实现降序排序。
data = [10, 4, 7, 1, 9]
data.sort(reverse=True)
print(data) # 输出: [10, 9, 7, 4, 1]
  • 自定义排序 (key 参数)
    • sort() 方法提供了一个 key 参数,允许指定一个函数,该函数会在比较元素之前对每个元素进行处理。key 函数会接收列表中的每个元素作为参数,并返回一个用于比较的值。这在排序复杂对象(如包含字典、自定义对象或元组的列表)时非常有用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 按照字符串长度排序
words = ["banana", "apple", "cherry", "kiwi"]
words.sort(key=len)
print(words) # 输出: ['kiwi', 'apple', 'banana', 'cherry']

# 按照字典中 'age' 键的值排序
people = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 35}
]
people.sort(key=lambda person: person["age"])
print(people)
# 输出: [{'name': 'Bob', 'age': 25}, {'name': 'Alice', 'age': 30}, {'name': 'Charlie', 'age': 35}]