Python官方文档网站
多读读能更加了解Python的各种语法与相关特性,有中文版的,碰到不懂的语法问题也可以上去查找一下。
Python官方文档
导包(import) 包与模块 import 用于将其他模块(module)或包(package)中的代码引入到当前文件中使用,从而复用其他模块 or 包的功能。
什么是模块(module),什么是包(package)
模块与包的关系,可以类比为文件和目录的关系,模块相当于目录下的文件,目录相当于包
module 内部包含变量、函数或类的代码,这些代码属于 module 定义的命名空间的一部分,两个不同的module可以有相同的变量名、函数或类。package 里面存放的是一个个module,也可以有子包(sub-package)。一个module定义一个命名空间,以便变量、函数和类可以在两个不同的module中具有相同的名称(很少),通过点号.访问主包中的module和子包,包目录下通常会有一个特殊的 __init__.py 文件,表名这个目录是一个Python包。
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解释器不会自动向上查找 demo1 或 Pythonic 下的其他模块。
PYTHONPATH 环境变量 :如果设置了 PYTHONPATH 环境变量,Python 会将其中指定的目录添加到 sys.path 中。可以用它来添加那些不在默认路径中的自定义模块目录。
标准库目录 :还会在安装目录下的标准库(E:\Python3.9\Lib)中搜索。这里包含了像 os、sys、json 等内置模块。
第三方库目录(site-packages) :最后,它会在 site-packages 目录(E:\Python3.9\Lib\site-packages)中搜索。这里是 pip 安装的第三方库(如 pandas、numpy)的存放位置。
如果 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']
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 module 或 from .. 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 语句不仅仅判断布尔值 True 或 False。它会遵循一套规则,将任何对象评估为布尔值! 。这种行为被称为真值测试 。
parameter 取决于什么?parameter最终的真假结果,完全取决于它所引用的那个对象的类型和内容。
if parameter: 什么时候为假(False)
一个对象parameter在 if 条件中被判断为假,通常是因为它“为空”或“为零”。以下这些内置数据类型会被评估为 False:
布尔值 False 本身
数字零: 整数 0、浮点数 0.0
空序列(Sequence):
空映射(Mapping):
空集合(Set): set()
特殊对象: None
if parameter: 什么时候为真(True)
如果一个对象不满足上述“为假”的条件,那么它就会被评估为 True。简单来说,任何非空、非零的对象 都会被认为是真的。
布尔值 True 本身
任何非零 的数字!!!: 1、-1、3.14 等
任何非空的序列:
非空字符串:"hello"
非空列表:[1, 2]
非空元组:(1,)
任何非空的映射:
任何非空的集合: {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) # 输出: 进入上下文 处理中... 资源 退出上下文,清理资源
常见的上下文管理器
文件/IO (open、gzip、zipfile…)
并发锁 (threading.Lock、asyncio.Lock…)
网络/数据库 (socket、数据库连接、requests.Session…)
临时配置 (decimal.localcontext、numpy.printoptions…)
异常/输出控制 (suppress、redirect_stdout…)
框架专用 (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 在不同的函数中是独立的。它扮演着两种角色:
打包(Pack) :在函数定义时,**kwargs 将所有未匹配的关键字参数收集 到一个字典中。
解包(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 是一个约定俗成的名称(args 是 arguments 的缩写,名称可以自定义如 *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 **kwargs 是 keyword 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]
当需要一个独立的对象副本时,就不能使用=,需要用到拷贝操作,即浅拷贝和深拷贝。
拷贝的问题只在处理“复合对象”时才重要,特别是当它包含“可变的子对象”时
浅拷贝 浅拷贝会创建一个新的复合对象 (例如列表、字典、集合等可变对象),对于原对象中包含的子对象 (或称为嵌套对象),浅拷贝只会复制它们的引用 ,而不是创建这些子对象的独立副本,在处理嵌套对象时需要格外注意!
对于不可变对象(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]]
深拷贝 深拷贝会创建一个全新的复合对象 ,并且递归地 复制原对象中所有嵌套的子对象,确保新对象和原对象之间完全独立 ,没有任何共享的引用。无论原对象及其子对象是可变还是不可变,修改新对象中的任何部分都不会影响 原对象,反之亦然。
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__() 方法的类,它可以像函数装饰器 一样使用 @ 类名来修饰其他函数或类,使实例可调用,从而充当装饰器
🔹 类装饰器的本质 :
类被用作装饰器,必须实现 __call__() 方法。
__call__() 方法会在被装饰函数调用时执行。
可以保持状态 (相比于函数装饰器)。
类装饰器的实现是调用了类里面的__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 路由);
一些框架(如 Flask 或 FastAPI 等)也使用了类似的装饰器语法来定义路由。这里的 @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')
静态方法 静态方法是一种与类相关联,但不依赖于类或实例状态 的方法。它不接收cls或self作为第一个参数。在Python中,使用@staticmethod装饰器来定义静态方法。
特点
没有默认参数 :不需要接收self或cls,它的行为就像一个普通的函数,只是它被归属于类的命名空间 。
可以通过类或实例调用 :调用方式与类方法类似,但它无法访问类或实例的属性或方法 。
主要用途 :用于存放与类逻辑相关但又不依赖于任何类或实例数据 的工具函数。这使得代码结构更清晰,将相关功能组织在一起。
可以为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()。所有具体的图形类(如Circle、Square)都必须实现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.
具体应用场景
抽象方法用于定义一个公共的接口 。它强制所有继承自抽象基类的子类必须实现该方法,从而确保不同子类都具有相同的行为。
假设正在开发一个文件处理器系统,需要处理不同类型的文件,比如PDF、Word、Text等。虽然每种文件的处理方式不同,但它们都必须有一个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 (类本身)
无
无(但在子类中实现时通常包含self或cls)
访问能力
能够访问类的属性和方法
不能访问类或实例的属性和方法
强制子类实现,行为由子类决定
主要用途
工厂方法,处理与类相关但不需要实例状态的操作,替代或扩展构造函数
组织与类逻辑相关但不依赖于任何类或实例数据的工具函数
定义接口或模板,强制子类遵循特定结构
调用方式
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高阶函数 高阶函数是指满足以下任意一个或同时满足两个条件的函数 :
接受一个或多个函数作为参数(最常见) 。
返回一个函数作为结果 。
这使得函数可以像普通参数(如整数、字符串)一样被传递、赋值和操作。高阶函数(如 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 中用于逐个访问可迭代对象的元素,提供以下关键用途:
内存效率 :迭代器一次只生成一个元素,适合处理大数据(如大文件或流式数据),不像列表将所有元素一次性加载到内存。
惰性求值 :按需生成数据,延迟计算,优化性能。
单向遍历 :支持一次遍历,适合单次处理场景(如日志分析)。
自定义迭代 :允许定义特定遍历逻辑(如跳跃、过滤)。
流式处理 :与生成器结合,处理实时或无限数据流。
为什么仅有可迭代对象不够? 可迭代对象(如列表、元组)可以通过 iter () 返回迭代器,但自身功能有限:
内存占用:
可迭代对象(如列表)一次性存储所有数据,内存开销大。
迭代器逐个生成数据,适合大文件或动态数据。
灵活性不足:
可迭代对象提供固定遍历顺序,无法动态调整。
迭代器通过 next () 可自定义遍历逻辑(如跳跃、条件过滤)。
一次性遍历:
可迭代对象可多次迭代,迭代器一次耗尽,适合临时数据处理。
动态生成:
可迭代对象通常是静态数据,迭代器(尤其是生成器)支持动态生成数据,如实时计算或流处理。
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__ 是与迭代概念相关的魔术方法,用于实现可迭代对象 和迭代器 。
__iter__ 方法
作用 :使对象成为可迭代对象 ,返回一个迭代器。
定义 :在类中定义 __iter__(self),返回一个实现了 __next__ 方法的迭代器对象(通常是 self)。
调用时机 :当对象用于 for 循环或 iter() 函数时,Python 调用 __iter__。
__next__ 方法
作用 :定义迭代器的行为,返回下一个元素。
定义 :在类中定义 __next__(self),每次调用返回迭代的下一个值,迭代结束时抛出 StopIteration 异常。
调用时机 :在迭代过程中,next() 函数或 for 循环会调用 __next__。
迭代器 (Iterator) 和 for ... in ... 循环 在 Python 中,for ... in ... 循环能够遍历任何**可迭代对象 (Iterable)**。当 for 循环遇到一个可迭代对象时,它会做以下几件事:
获取迭代器: 它会调用可迭代对象的 __iter__() 方法,从而获取一个迭代器 (Iterator) 对象。
反复调用 next(): 循环会反复地调用这个迭代器的 __next__() 方法来获取下一个元素。
捕获 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 迭代器给出下一个 元素。
此时,products 和 prices 都已经没有更多元素了。
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内置的list、set、dict其实都是类,但是创建列表、集合、字典对象时,无需自定义类。当然,有的类并不是 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]
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}]