ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python标准库:被低估的原生基建与工程实践指南

Python标准库:被低估的原生基建与工程实践指南 1. 为什么“Python标准库”不是一句空话而是你每天都在用却浑然不觉的底层基建你写过import json解析配置文件用过os.path.join()拼路径调过datetime.now()获取当前时间甚至只是print(hello)——这些动作背后没有 pip install没有 GitHub clone没有 PyPI 下载全靠 Python 安装时自带的一整套“出厂预装工具箱”。这个工具箱就是 Python 标准库Python Standard Library。它不是某个可选插件而是 Python 解释器启动时自动加载的、与解释器版本强绑定的原生能力集合。它覆盖了文件系统操作、网络通信、数据序列化、加密解密、正则匹配、多线程/多进程、日期时间处理、文本编码转换、HTML/XML 解析、单元测试框架等超过 200 个模块总代码量超百万行是 CPython 解释器不可分割的“血肉”。很多人误以为“学 Python 就是学第三方库”结果一上来就猛啃 pandas、requests、flask却连pathlib怎么替代os.path都不清楚遇到UnicodeDecodeError只会百度搜“乱码怎么解决”而不知道codecs模块早已内置了完整的编码探测与转换逻辑。这种认知偏差直接导致两个典型后果一是项目依赖爆炸一个简单脚本硬生生拉进 30 个包部署时因环境差异频繁报错二是基础能力薄弱面对subprocess.run()的返回值处理、logging的层级配置、argparse的参数校验等原生功能要么绕道用第三方方案要么写一堆冗余胶水代码。我见过最典型的案例是一个运维脚本需要读取日志并按时间过滤开发者花了三天集成loguru和pandas最后发现datetime.strptime()re.findall()三行原生代码就能搞定且零依赖、启动快、无兼容风险。标准库的价值从来不在“炫技”而在“稳”和“省”。它经过 CPython 团队十年以上持续打磨API 设计遵循“显式优于隐式”原则文档完整度远超 99% 的第三方库且与解释器深度耦合——比如sys模块直接暴露解释器状态gc模块提供底层垃圾回收控制ctypes能无缝调用 C 动态库。这些能力第三方库要么无法触及要么实现成本极高。更重要的是标准库是跨平台的终极保障你在 Windows 上用shutil.copytree()复制目录在 Linux 上用subprocess.Popen()启动进程在 macOS 上用plistlib读取系统配置行为完全一致无需额外适配。这不是“能用”而是“必须用”——当你需要写一个能在树莓派、服务器、Docker 容器里都稳定运行的轻量级服务时标准库就是你唯一的、最可靠的基础设施。2. 标准库不是“模块列表”而是按问题域组织的精密协作网络把标准库当成一个“模块清单”去死记硬背是入门者最大的误区。它真正的设计哲学是以现实问题为锚点构建模块间的协同关系。比如“处理文件”这件事标准库绝不是只给你一个open()函数就完事而是提供了一整套分层协作体系底层 I/O 抽象层io模块定义了TextIOBase、BufferedIOBase等抽象基类所有文件对象如io.TextIOWrapper都继承自它们确保接口一致性路径操作层os.path提供字符串路径拼接与解析如os.path.dirname()而pathlib则封装为面向对象的Path类支持链式调用Path(/tmp).joinpath(data.txt).resolve()两者共存满足不同习惯文件系统操作层shutil负责高级操作复制、移动、归档glob负责通配符匹配tarfile和zipfile分别处理压缩包它们共享os模块的底层系统调用但各自专注单一职责异常处理层所有文件操作抛出的异常如FileNotFoundError、PermissionError都继承自OSError你可以用统一的except OSError:捕获而不必为每个模块单独写except FileNotFoundError:。这种分层不是随意堆砌而是有明确的演进逻辑。以pathlib为例它在 Python 3.4 引入正是为了解决os.path字符串操作易出错的问题比如路径拼接时忘记加/。但标准库没有废弃os.path而是让两者并存——因为os.path在性能敏感场景如高频路径计算仍有优势而pathlib更适合可读性要求高的业务逻辑。这种“新旧共存、各司其职”的设计体现了标准库对真实工程需求的深刻理解它不追求技术先进性而追求向后兼容性与场景适配性。再看网络编程领域socket模块提供最底层的套接字 APIhttp.client基于它封装 HTTP 协议urllib.request进一步封装 URL 请求支持代理、重定向、Cookie而xmlrpc.client则基于http.client实现 RPC 通信。整个链条像一条流水线下层模块提供原子能力上层模块组合封装形成从“字节流”到“业务请求”的完整映射。你不需要记住所有模块名但必须理解这条链路——当requests库在某些嵌入式环境无法安装时urllib.request就是你兜底的救命稻草当需要调试 TCP 连接超时问题时socket.settimeout()的底层控制权是任何高级库都无法替代的。提示标准库模块间存在强依赖关系但绝不循环依赖。例如json模块依赖decimal处理浮点数精度decimal依赖numbers数字类型抽象但numbers不依赖json。这种单向依赖保证了模块可独立使用也降低了学习门槛——你可以先掌握json和datetime再逐步深入decimal和fractions。3. 从“能用”到“用好”五个被严重低估的标准库模块实战精要很多开发者停留在“知道有这个模块”的层面却从未深挖其设计精妙之处。以下五个模块代表了标准库中“看似简单、实则深邃”的典型掌握它们能让你的代码质量跃升一个台阶。3.1pathlib告别字符串拼接拥抱面向对象的路径操作os.path的字符串操作方式本质是把路径当作普通字符串处理极易出错# 错误示范手动拼接跨平台风险高 config_path os.path.join(os.getcwd(), conf, app.json) # 在 Windows 上生成 C:\project\conf\app.json在 Linux 上生成 /home/user/project/conf/app.json # 但如果当前路径末尾有 /或 conf 目录名含 /结果可能变成 C:\project//conf\app.json —— 虽然多数系统能容忍但逻辑不严谨pathlib将路径视为对象所有操作都通过方法链完成from pathlib import Path # 创建路径对象自动处理平台差异 base_dir Path(__file__).parent # 当前文件所在目录 config_path base_dir / conf / app.json # 使用 / 运算符拼接清晰直观 # 安全读取文件 if config_path.exists(): data json.loads(config_path.read_text(encodingutf-8)) else: raise FileNotFoundError(fConfig not found: {config_path}) # 创建目录exist_okTrue 避免重复创建异常 log_dir base_dir / logs log_dir.mkdir(parentsTrue, exist_okTrue) # parentsTrue 支持多级目录创建关键优势在于语义明确性/运算符专用于路径拼接exists()明确表达“检查存在性”read_text()强制指定编码避免open()的默认编码陷阱。更强大的是它的模式匹配能力# 查找所有 .py 文件递归 py_files list(base_dir.rglob(*.py)) # 查找最近修改的 5 个日志文件 log_files sorted(base_dir.glob(logs/*.log), keylambda x: x.stat().st_mtime, reverseTrue)[:5]这比glob.glob()返回字符串列表、再手动os.path.getmtime()计算代码量减少 50%可读性提升 100%。3.2dataclasses用声明式语法替代样板化的类定义在 Python 3.7 之前定义一个带属性的数据容器类需要大量样板代码class User: def __init__(self, name: str, age: int, email: str): self.name name self.age age self.email email def __repr__(self): return fUser(name{self.name}, age{self.age}, email{self.email}) def __eq__(self, other): if not isinstance(other, User): return False return self.name other.name and self.age other.age and self.email other.emaildataclasses模块将这一切简化为一行装饰器from dataclasses import dataclass dataclass class User: name: str age: int email: str它自动生成__init__、__repr__、__eq__还支持更多高级特性dataclass class User: name: str age: int email: str # 默认值字段 is_active: bool True # 不参与比较和 repr 的字段 _id: int field(default0, compareFalse, reprFalse) # 延迟计算字段类似 property但只计算一次 full_name: str field(initFalse) def __post_init__(self): self.full_name f{self.name} ({self.age})dataclass的核心价值是将关注点从“如何实现”转移到“数据是什么”。它强制你思考字段的类型、默认值、是否参与比较等语义信息而非纠结于构造函数的书写细节。在 API 响应解析、配置管理、数据库模型映射等场景它能让你的代码结构更清晰、错误更早暴露类型注解配合 mypy 静态检查。3.3contextlib用contextmanager写出可复用的上下文管理器with语句是 Python 最优雅的资源管理机制但为每个资源写__enter__/__exit__方法过于繁琐。contextlib提供了两种高效方案方案一contextmanager装饰器推荐from contextlib import contextmanager contextmanager def temporary_directory(): 创建临时目录退出时自动删除 import tempfile import shutil temp_dir tempfile.mkdtemp() try: yield temp_dir # yield 之前的代码在 with 进入时执行 finally: shutil.rmtree(temp_dir) # yield 之后的代码在 with 退出时执行无论是否异常 # 使用 with temporary_directory() as tmp: (Path(tmp) / test.txt).write_text(hello) # 退出 with 块时temp_dir 自动被删除方案二closing()和suppress()快速兜底from contextlib import closing, suppress # closing() 确保对象的 close() 方法被调用 from urllib.request import urlopen with closing(urlopen(http://example.com)) as response: data response.read() # suppress() 忽略特定异常比 try/except 更简洁 from pathlib import Path with suppress(FileNotFoundError): Path(/tmp/old.log).unlink() # 如果文件不存在静默忽略contextlib的精髓在于将“资源生命周期管理”这一横切关注点从业务逻辑中彻底剥离。你不再需要在每个函数里写try/finally而是用一个装饰器或一个函数调用就完成了资源的安全释放。这种抽象让代码主干聚焦于核心业务大幅提升可维护性。3.4functoolslru_cache和partial是性能与复用的双引擎functools模块常被忽视但它提供了两个改变游戏规则的工具lru_cache函数级缓存让递归/计算密集型函数飞起来from functools import lru_cache lru_cache(maxsize128) # 缓存最近 128 次调用结果 def fibonacci(n): if n 2: return n return fibonacci(n-1) fibonacci(n-2) # 未加缓存时fibonacci(35) 需要数秒加缓存后毫秒级返回 print(fibonacci(100)) # 第一次计算后后续调用直接返回缓存值lru_cache的底层是哈希表双向链表maxsizeNone表示无限制缓存typedTrue可区分不同类型的参数如1和1.0。它适用于纯函数无副作用、输入相同输出必相同是优化算法性能的首选。partial函数参数预设消除重复调用的样板代码from functools import partial import json # 为 json.dumps 预设常用参数 safe_json_dumps partial(json.dumps, ensure_asciiFalse, indent2) # 无需每次都写冗长参数 data {name: 张三, score: 95} print(safe_json_dumps(data)) # 输出格式化后的中文 JSON # 在回调函数中固定部分参数 from tkinter import Button def log_action(action, user_id, timestamp): print(f[{timestamp}] User {user_id} performed {action}) # 为按钮点击事件预设 action 参数 button Button(textSave, commandpartial(log_action, save, user_id123))partial的本质是“函数工厂”它不执行原函数而是返回一个新函数该函数调用时自动填充预设参数。这比写 lambda 更清晰lambda: log_action(save, 123)比写闭包更简洁是构建可复用组件的利器。3.5typing类型提示不是摆设而是 IDE 和静态检查的燃料typing模块尤其在 Python 3.5让类型提示从“注释”变为“可执行契约”from typing import List, Dict, Optional, Union, Callable, TypeVar, Generic # 基础类型 def process_users(users: List[Dict[str, Union[str, int]]]) - Optional[str]: if not users: return None return users[0].get(name) # 泛型类 T TypeVar(T) class Stack(Generic[T]): def __init__(self) - None: self._items: List[T] [] def push(self, item: T) - None: self._items.append(item) def pop(self) - T: # 返回类型与泛型参数一致 return self._items.pop() # 可调用类型 def apply_transform(data: List[int], transform: Callable[[int], int]) - List[int]: return [transform(x) for x in data] apply_transform([1, 2, 3], lambda x: x * 2) # IDE 能推断 transform 参数类型为 int - int类型提示的价值在于提前暴露错误。PyCharm、VS Code 等 IDE 能基于提示提供精准补全、跳转和错误标记mypy工具可在运行前发现类型不匹配如str传给期望int的参数。更重要的是它让函数签名成为自文档化接口——看到def load_config(path: Path) - Dict[str, Any]你就知道输入是路径对象输出是字典无需翻阅文档。这在团队协作和长期维护中节省的时间远超写提示本身。4. 标准库的“暗礁区”五个必须避开的常见陷阱与实战对策标准库虽稳但并非没有坑。这些陷阱往往源于对模块设计意图的误解或对底层机制的无知。踩过之后我才真正理解“稳”背后的代价。4.1datetime时区陷阱naive与aware时间的生死线datetime模块最致命的坑是naive朴素时间与aware感知时间的混淆from datetime import datetime, timezone # naive 时间无时区信息 naive_time datetime(2023, 1, 1, 12, 0, 0) print(naive_time.tzinfo) # None # aware 时间有时区信息 aware_time datetime(2023, 1, 1, 12, 0, 0, tzinfotimezone.utc) print(aware_time.tzinfo) # UTC # 危险操作naive 时间与 aware 时间直接比较或计算 try: result aware_time - naive_time # TypeError: cant subtract offset-naive and offset-aware datetimes except TypeError as e: print(e)问题根源在于naive时间无法确定其代表的绝对时刻是北京时间纽约时间而aware时间明确指向 UTC 时间轴。标准库强制要求两者不能混用这是为了防止时区转换错误导致的业务逻辑灾难如订单超时判断错误。对策全域统一使用aware时间from datetime import datetime, timezone import zoneinfo # Python 3.9推荐替代 pytz # 创建本地时区 aware 时间推荐 beijing_tz zoneinfo.ZoneInfo(Asia/Shanghai) local_time datetime(2023, 1, 1, 12, 0, 0, tzinfobeijing_tz) # 转换为 UTC存储和传输的标准 utc_time local_time.astimezone(timezone.utc) # 从字符串解析时务必指定时区 from dateutil import parser # 第三方但处理复杂字符串更可靠 # 或使用标准库需手动指定 dt_str 2023-01-01T12:00:0008:00 parsed datetime.fromisoformat(dt_str) # Python 3.7 支持带时区的 ISO 格式记住所有进入系统的用户输入时间第一件事就是赋予其时区所有存储和计算统一用 UTC所有展示给用户的再转换回本地时区。这是处理时间问题的黄金法则。4.2subprocess的 shell 注入风险shellTrue是把双刃剑subprocess模块是调用外部命令的主力但shellTrue参数极易引发安全漏洞import subprocess # 危险用户输入直接拼接进 shell 命令 user_input test; rm -rf / # 恶意输入 cmd fecho {user_input} /tmp/output.txt subprocess.run(cmd, shellTrue) # 执行了 echo test; rm -rf /系统被删 # 安全做法禁用 shell用列表传参 subprocess.run([echo, user_input, , /tmp/output.txt]) # 错误 是 shell 特性列表模式不识别 # 正确将重定向等 shell 特性用标准库原生方式实现 with open(/tmp/output.txt, w) as f: subprocess.run([echo, user_input], stdoutf)shellTrue会启动/bin/shLinux/macOS或cmd.exeWindows将字符串作为 shell 命令执行其中的;、|、$()等字符会被 shell 解析导致任意命令执行。这是 Web 应用中常见的远程代码执行RCE漏洞。对策永远优先使用shellFalse默认将命令及其参数拆分为列表[ls, -l, /home]而非ls -l /home需要管道、重定向等 shell 功能时用subprocess.PIPE和 Python 文件操作组合实现确实需要 shell 功能如 glob 匹配则对用户输入进行严格白名单过滤或转义shlex.quote()import shlex user_input test; rm -rf / safe_input shlex.quote(user_input) # 转义为 test; rm -rf / cmd fecho {safe_input} /tmp/output.txt subprocess.run(cmd, shellTrue) # 现在 echo 的参数是字面量 test; rm -rf /不会执行 rm4.3json的编码陷阱ensure_asciiFalse与default参数的正确姿势json.dumps()默认ensure_asciiTrue会将非 ASCII 字符如中文转义为\uXXXX导致输出不可读import json data {name: 张三, city: 北京} print(json.dumps(data)) # {name: \u5f20\u4e09, city: \u5317\u4eac} # 修复设置 ensure_asciiFalse print(json.dumps(data, ensure_asciiFalse)) # {name: 张三, city: 北京}但这只是开始。更大的坑是default参数的误用# 错误试图在 default 中处理所有类型 def bad_default(obj): if isinstance(obj, datetime): return obj.isoformat() elif isinstance(obj, Path): return str(obj) else: return str(obj) # 危险str(obj) 可能触发无限递归如自定义类有 __str__ 调用自身 # 正确只处理已知类型对未知类型抛出 TypeError让 json 模块报错 def good_default(obj): if isinstance(obj, datetime): return obj.isoformat() elif isinstance(obj, Path): return str(obj) raise TypeError(fObject of type {type(obj)} is not JSON serializable)default函数的职责是将特定类型转换为 JSON 原生类型dict, list, str, int, float, bool, None而不是兜底转换一切。盲目return str(obj)会导致json模块再次尝试序列化字符串陷入死循环。标准库的设计哲学是“显式失败”而非“隐式兜底”。4.4threading的 GIL 误解多线程 ≠ 并行计算Python 的全局解释器锁GIL是标准库多线程的“阿喀琉斯之踵”。很多开发者误以为threading.Thread能加速 CPU 密集型任务import threading import time def cpu_intensive_task(): # 纯计算无 I/O total 0 for i in range(10**7): total i * i return total # 单线程 start time.time() cpu_intensive_task() cpu_intensive_task() print(fSingle thread: {time.time() - start:.2f}s) # 多线程错误预期应该更快 start time.time() t1 threading.Thread(targetcpu_intensive_task) t2 threading.Thread(targetcpu_intensive_task) t1.start() t2.start() t1.join() t2.join() print(fTwo threads: {time.time() - start:.2f}s) # 结果几乎一样甚至更慢原因在于GIL 确保同一时刻只有一个线程执行 Python 字节码。CPU 密集型任务会一直持有 GIL其他线程只能等待。threading的真正价值在于I/O 密集型任务的并发如网络请求、文件读写此时线程在等待 I/O 时会释放 GIL让其他线程运行。对策CPU 密集型任务用multiprocessingfrom multiprocessing import Process import time def cpu_task(): total 0 for i in range(10**7): total i * i return total # 多进程真正并行 start time.time() p1 Process(targetcpu_task) p2 Process(targetcpu_task) p1.start() p2.start() p1.join() p2.join() print(fTwo processes: {time.time() - start:.2f}s) # 时间减半双核multiprocessing绕过 GIL每个进程有独立的 Python 解释器和内存空间是 CPU 并行的唯一标准库方案。4.5logging的配置陷阱basicConfig的隐藏覆盖规则logging.basicConfig()是快速启用日志的捷径但它的调用时机和参数有严格限制import logging # 第一次调用 basicConfig 生效 logging.basicConfig(levellogging.INFO, format%(levelname)s: %(message)s) logging.info(This works) # 第二次调用被忽略配置已生效无法修改 logging.basicConfig(levellogging.DEBUG, format%(asctime)s - %(levelname)s - %(message)s) logging.debug(This will NOT appear!) # 因为 level 仍是 INFO # 正确做法用 dictConfig 或直接操作 Logger 对象 import logging.config LOGGING_CONFIG { version: 1, disable_existing_loggers: False, formatters: { standard: {format: %(asctime)s [%(levelname)s] %(name)s: %(message)s}, }, handlers: { default: {level: INFO, class: logging.StreamHandler, formatter: standard}, }, loggers: { : {handlers: [default], level: INFO, propagate: False}, } } logging.config.dictConfig(LOGGING_CONFIG)basicConfig的设计是“一次性配置”一旦logging模块被其他代码如第三方库初始化过它就失效。生产环境必须用dictConfig或fileConfig进行显式、可重复的配置。5. 标准库的进化脉络从 Python 2 到 3.12哪些模块值得你重点关注标准库不是静态化石而是持续演化的活体。理解其演进方向能帮你避开即将淘汰的 API拥抱未来主流。5.1 Python 2 到 3 的断崖式升级urllib和print的范式转移Python 3 的最大变革是统一字符串模型。Python 2 中str是字节序列unicode是文本导致urllib模块分裂为urllib处理 URL 编码、urllib2处理 HTTP 请求、urlparse解析 URL。Python 3 将其整合为urllib包并严格区分str文本和bytes字节# Python 2 import urllib2 response urllib2.urlopen(http://example.com) html response.read() # 返回 str字节 # 若需文本需手动 decode text html.decode(utf-8) # Python 3 import urllib.request response urllib.request.urlopen(http://example.com) html response.read() # 返回 bytes text html.decode(utf-8) # 显式解码为 str # 或直接读取文本 text response.read().decode(utf-8)print也从语句变为函数强制括号和显式参数sep,end,file提升了可组合性。这些变化不是“改名”而是对文本/字节二元性的正视是 Python 成熟的标志。5.2 Python 3.4 的现代化浪潮pathlib、enum、statistics的崛起Python 3.4 引入pathlib标志着标准库从“过程式”向“面向对象”转型3.4 还引入enum模块终结了用classint模拟枚举的混乱from enum import Enum, auto class Color(Enum): RED auto() # 自动赋值 1, 2, 3... GREEN auto() BLUE auto() print(Color.RED.value) # 1 print(Color.RED.name) # RED print(Color.RED) # Color.RED3.4 的statistics模块则填补了数学统计的空白无需numpy即可计算均值、中位数、方差import statistics data [1, 2, 2, 3, 4, 4, 4, 5] print(statistics.mean(data)) # 3.0 print(statistics.median(data)) # 3.5 print(statistics.mode(data)) # 45.3 Python 3.8 的生产力革命walrus运算符与importlib.metadataPython 3.8 的海象运算符:让条件表达式中复用计算结果成为可能# 传统写法重复计算 if (n : len(data)) 10: print(fList is too long: {n} items) # 3.8 新写法一行搞定 if len(data) 10: print(fList is too long: {len(data)} items) # 重复调用 len()3.8 还引入importlib.metadata3.12 移至importlib.metadata统一了包元数据查询from importlib import metadata # 查询已安装包的版本 print(metadata.version(requests)) # 查询包的入口点如 CLI 工具 entry_points metadata.entry_points(groupconsole_scripts) for ep in entry_points: print(ep.name, ep.value)这取代了pkg_resources来自 setuptools解决了其性能慢、依赖重的问题。5.4 Python 3.12 的前沿探索tomllib与graphlibPython 3.12 将tomllibTOML 解析器纳入标准库回应了pyproject.toml成为 Python 项目事实标准的趋势import tomllib # 直接解析 pyproject.toml with open(pyproject.toml, rb) as f: config tomllib.load(f) print(config[build-system][requires])同时graphlib模块提供了有向无环图DAG的拓扑排序为构建系统、依赖解析等场景提供了原生支持from graphlib import TopologicalSorter # 构建依赖图A 依赖 B 和 CB 依赖 C graph {A: [B, C], B: [C], C: []} sorter TopologicalSorter(graph) order list(sorter.static_order()) # [C, B, A]这些新增模块清晰地表明标准库的演进方向拥抱现代开发实践TOML 配置、DAG 依赖同时保持极简主义不造轮子只提供最通用的原语。6. 如何构建你的标准库知识图谱从“查文档”到“直觉驱动”的学习路径掌握标准库不是背诵模块列表而是建立一种“遇到问题直觉指向某个模块”的肌肉记忆。我的经验是分三步走6.1 第一阶段按“问题域”建立索引而非按字母顺序不要从aifc模块开始学。打开 Python 官方文档的 标准库索引 按主题分类浏览数据持久化json,pickle,sqlite3,shelve,dbm文本处理re,string,textwrap,difflib,unicodedata网络与 IPCsocket,http,urllib,xmlrpc,asyncio文件与目录os,shutil,pathlib,glob,tarfile,zipfile日期与时间datetime,time,calendar,zoneinfo算法与数据结构collections,heapq,bisect,array,queue并发与并行threading,multiprocessing,concurrent.futures,asyncio开发工具unittest,doctest,pdb,profile,trace为每个大类记下 2-3 个核心模块及其不可替代性。例如“文本处理”中re是正则引擎difflib是差异对比专家unicodedata是 Unicode 字符数据库——它们解决的是完全不同的问题不能互相替代。6.2 第二阶段用“最小可行代码”验证每个模块的核心能力对每个目标模块写一段 5 行以内的代码验证其最核心功能# collections.defaultdict from collections import defaultdict d defaultdict(list) d[key].append(value) # 无需检查 key 是否存在 print(d) # {key: [value]} # heapq最小堆 import heapq heap [3, 1, 4, 1, 5] heapq.heapify(heap) # 原地转为堆 print(heapq.heappop(heap)) # 1最小元素 #
返回列表