Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
上一篇我们学习了 Python 模块,了解了如何使用
import导入模块、如何通过from...import导入指定对象,以及如何把一个 Python 脚本拆分成多个模块。但问题来了:导入一个模块之后,怎么知道它里面到底有哪些函数、变量和属性?
比如,你导入了 Python 标准库中的
math模块,知道它可以进行数学计算,但除了sqrt(),还有哪些函数可以使用?再比如,在 Linux 运维自动化脚本中,你想了解
os、sys或shutil模块提供了哪些功能,难道每次都要去查文档吗?其实,Python 已经提供了一个非常方便的内置函数:
dir()。今天,我们就从最简单的例子开始,逐步掌握
dir()的用法,并弄清楚它与globals()、locals()以及模块中的__all__到底有什么区别。
认识 dir() 函数
在学习 dir() 之前,我们先理解一个问题:Python 是怎么知道一个模块中有哪些名称的?
还记得上一篇学习的 import 吗?
当我们执行下面的代码时:
1 | import math |
Python 会导入 math 模块,并让我们通过 math 这个名称访问模块提供的功能。
例如:
1 | import math |
运行结果:
1 | 4.0 |
这里的 sqrt 是一个函数,pi 是一个表示圆周率的常量。
但是,math 模块中还有很多其他名称。如果不查阅文档,我们怎样快速了解它们呢?
这时候就可以使用 dir()。
dir() 是 Python 的内置函数,不需要额外导入任何模块。它可以返回一个对象相关名称组成的列表,帮助我们探索对象提供了哪些属性。
基本语法如下:
1 | dir([object]) |
其中:
object:可选参数,表示要查看的对象。- 传入对象时,
dir()通常返回该对象相关的名称列表。 - 不传参数时,
dir()返回当前局部作用域中的名称,具体结果与调用位置有关。
这里的“名称”不只是函数,还可能包括变量、类、方法以及其他属性。
需要注意,dir() 的主要目的是方便我们探索对象,它返回的名称不一定是该对象所有可能属性的完整清单。对于自定义对象,结果还可能受到对象自身 __dir__() 方法的影响。
使用 dir() 查看模块
我们先从最容易理解的 math 模块开始。
创建一个 Python 文件:
1 | mkdir -p ~/python-dir-demo |
在文件中输入以下代码:
1 | import math |
保存后运行:
1 | python dir_math.py |
运行后,你会看到一长串名称,例如:
1 | ['__doc__', '__loader__', '__name__', '__package__', |
上面只是示意,实际输出会更长,具体名称可能随 Python 版本和运行环境有所不同。
先不用急着记住这些名称,我们重点关注几个内容:
sqrt:计算平方根。sin、cos:三角函数。floor:向下取整。ceil:向上取整。pi:圆周率。e:自然常数。__name__、__doc__:模块自身的特殊属性。
通过 dir(math),我们就能快速了解 math 模块提供了哪些名称,而不需要一开始就翻阅完整的官方文档。
不过,这里有一个细节需要注意:dir() 返回的名称不一定都是可以直接调用的函数。
例如:
1 | import math |
输出:
1 | 3.141592653589793 |
math.pi 是一个数值,不能像函数一样写成 math.pi()。
因此,看到 dir() 的输出后,我们还需要进一步确认每个名称的用途。
dir() 不只是查看模块
前面的示例都传入了模块对象,但 dir() 并不局限于模块。
它还可以用于查看字符串、列表等对象提供的名称。
例如:
1 | text = "Linux" |
你会看到类似下面这些名称:
1 | ['__add__', '__class__', '__contains__', '__doc__', |
具体列表可能因 Python 版本而有所不同。
这些名称中,有不少是我们熟悉的字符串操作方法:
upper():将字符串转换为大写。lower():将字符串转换为小写。split():拆分字符串。replace():替换字符串中的内容。strip():去掉字符串两端的空白字符。
例如,在 Linux 运维中,我们经常需要处理命令输出、日志内容或者服务名称。
1 | service = " nginx " |
输出:
1 | nginx |
这里的 strip() 和 upper() 都是字符串对象提供的方法。
我们可以先通过 dir(text) 查看有哪些可用名称,再根据实际需求选择相应的方法。
当然,dir() 本身并不会告诉我们每个方法的具体参数和使用规则。它更像是一份快速索引,帮助我们知道“有哪些东西可以进一步了解”。
不传参数的 dir() 到底查看什么?
前面我们使用了 dir(math) 和 dir(text),都是把对象传给 dir(),让它列出该对象相关的名称。
如果不传参数呢?
1 | dir() |
不带参数的 dir() 会尝试返回当前局部作用域中的名称。在常见的模块和函数场景中,可以这样理解:
- 在模块顶层调用时,返回当前模块命名空间中的名称。此时,模块顶层的局部命名空间与全局命名空间相对应。
- 在函数内部调用时,返回当前函数局部作用域中的名称。
- 在类定义体等特殊执行环境中,结果还可能受到当前执行环境的影响。
这里需要注意:不带参数的 dir() 主要用于列出当前局部作用域中的名称,它不等于列出所有能够通过变量查找规则访问到的名称。
在模块顶层调用 dir()
创建文件 dir_scope.py:
1 | name = "Linux" |
运行:
1 | python dir_scope.py |
输出中会包含类似下面这些名称:
1 | ['__annotations__', '__builtins__', '__cached__', |
实际输出可能因 Python 版本和执行方式不同而有所差异。
为什么会出现 name、count 和 check_server?
因为它们都是在模块顶层定义的名称:
1 | name = "Linux" |
其中,name 和 count 是模块级变量,check_server 是模块级函数。
在模块顶层调用不带参数的 dir() 时,这些名称通常都会出现在结果中。
注意,定义函数时,函数体并不会立即执行。因此,函数内部的 hostname 此时不会出现在模块顶层的 dir() 结果中。
在函数内部调用 dir()
现在修改 dir_scope.py:
1 | name = "Linux" |
运行:
1 | python dir_scope.py |
模块顶层的结果会包含 name、count 和 check_server 等名称;函数内部的结果则会包含 hostname 和 port 等局部名称。
这里仅列出关键名称,完整输出还可能包含函数参数以及其他局部名称。
为什么两次调用的结果不同?
因为它们位于不同的作用域中。
当程序执行:
1 | print(dir()) |
时,调用发生在模块顶层,因此返回模块当前命名空间中的名称。
而执行:
1 | def check_server(): |
时,调用发生在函数内部,因此返回当前局部作用域中的名称。在这个例子中,hostname 和 port 都是函数的局部变量。
这里还要区分两个概念:作用域中的名称,以及变量查找时能够访问到的名称。
例如,name 是模块级变量。在函数内部,如果没有同名的局部变量,通常也可以直接读取它:
1 | name = "Linux" |
输出:
1 | Linux |
但是,在函数内部调用不带参数的 dir(),并不会因此把所有外层作用域中的名称都列出来。它主要反映当前局部作用域中的名称。
可以用下面的流程理解:
flowchart TD
A["调用 dir() 不传参数"] --> B{"调用位置"}
B -->|"模块顶层"| C["列出模块当前命名空间中的名称"]
B -->|"函数内部"| D["列出函数当前局部作用域中的名称"]
C --> E["返回名称列表"]
D --> E如果需要明确查看模块的全局命名空间,应该使用 globals();如果需要查看当前局部命名空间,可以使用 locals()。
dir()、globals() 与 locals() 有什么区别?
这三个函数看起来有些相似,但返回结果和用途并不相同。
使用 globals() 查看全局命名空间
globals() 返回当前模块的全局命名空间字典。
例如,创建文件 scope_demo.py:
1 | name = "Linux" |
运行:
1 | python scope_demo.py |
输出:
1 | 全局变量 name: Linux |
这里的:
1 | globals()["name"] |
表示从当前模块的全局命名空间字典中,读取键为 name 的值。
需要注意,即使在函数内部调用 globals(),它返回的仍然是当前模块的全局命名空间,而不是函数的局部命名空间。
例如:
1 | name = "Linux" |
输出:
1 | globals 中有 name: True |
原因很简单:name 定义在模块顶层,而 hostname 是函数局部变量。
这也说明了 globals() 与不带参数的 dir() 并不是同一回事。
使用 locals() 查看局部命名空间
locals() 用于获取当前局部命名空间的映射。
例如:
1 | def check_server(): |
输出:
1 | {'hostname': 'server01', 'port': 22} |
在这个函数中,locals() 返回的映射包含 hostname 和 port 等局部变量及其值。
与 dir() 返回名称列表不同,locals() 返回的是名称与对象之间的映射关系。
需要注意,在模块顶层调用 locals() 时,它与 globals() 返回的是同一个模块命名空间。函数内部则要根据当前执行环境理解其结果。
另外,在函数内部,locals() 适合用来检查局部变量,但不要依赖修改它返回的映射来改变函数局部变量,因为这种修改在优化后的函数作用域中并不保证生效。
三者的区别
| 函数 | 返回结果 | 主要用途 |
|---|---|---|
dir() | 名称列表 | 查看对象相关的名称;不带参数时查看当前局部作用域中的名称 |
globals() | 全局命名空间字典 | 获取当前模块的全局名称及其对应对象 |
locals() | 局部命名空间映射 | 查看当前局部环境中的名称及其对应对象 |
初学阶段,可以这样记忆:
- 想了解模块提供了哪些名称,使用
dir(module)。 - 想查看当前局部作用域中有哪些名称,使用不带参数的
dir()。 - 想获取当前模块的全局命名空间,使用
globals()。 - 想查看函数当前的局部变量及其值,使用
locals()。
如何判断一个名称是函数还是变量?
现在,我们已经能够列出模块中的名称了。
但是,看到一个名称后,怎样知道它究竟是函数、变量,还是其他类型的对象?
例如:
1 | import math |
输出中有 sqrt,也有 pi。
如果我们希望进一步判断它们的类型,可以结合 type() 和 callable() 使用。
创建文件 dir_inspect.py:
1 | import math |
运行:
1 | python dir_inspect.py |
在常见的 CPython 环境中,输出类似:
1 | sqrt 的类型: <class 'builtin_function_or_method'> |
我们逐行解释。
type(math.sqrt) 用于查看 sqrt 对象的类型。在当前常见的 CPython 环境中,它通常显示为 builtin_function_or_method。
type(math.pi) 则返回 float,因为 pi 是浮点数。
callable() 用于判断一个对象是否可以被调用。对于这里的两个对象,math.sqrt 可以调用,而 math.pi 不可以。
注意,callable() 返回 True 并不意味着调用一定成功。例如,传入错误的参数仍然可能导致异常。
这套组合非常实用:
1 | dir(obj) |
这里的 obj 和 attr 只是示意名称,使用时需要替换成实际的对象和属性。
先查看名称,再检查类型,最后判断是否可以调用。对于初学者来说,这是一种探索陌生模块的简单方法。
结合 help() 查看具体用法
dir() 可以告诉我们有哪些名称,但无法直接解释每个函数应该传入什么参数。
例如,看到 math.sqrt 后,我们可能会问:
- 它需要几个参数?
- 参数应该是什么类型?
- 它返回什么结果?
- 参数不合法时会发生什么?
这时候就可以使用 Python 内置的 help() 函数。
例如:
1 | import math |
执行后,Python 会显示 sqrt() 的帮助信息,其中包含函数的用途、参数说明和返回值等文档信息。具体显示内容取决于该对象提供的帮助文档。
你也可以直接查询整个模块:
1 | import math |
不过,整个模块的帮助信息可能很长。
如果只是想了解一个具体函数,通常建议直接查询它:
1 | import math |
在 Python 交互式解释器中,还可以先导入模块,再逐步探索:
1 | import math |
整个过程可以概括为:
flowchart LR
A["导入模块"] --> B["使用 dir() 查看名称"]
B --> C["选择感兴趣的名称"]
C --> D["使用 type() 和 callable() 检查"]
D --> E["使用 help() 查看说明"]
E --> F["编写代码并验证结果"]这样,即使遇到一个之前没有使用过的模块,也可以先自己探索,再决定是否查阅更详细的官方文档。
理解 dir() 与 all 的区别
上一篇学习模块时,我们接触过 from module import * 这种导入方式。
例如:
1 | from math import * |
这种写法会根据模块的导入规则,将相应名称导入当前命名空间。
那么,问题来了:模块中有那么多名称,使用 import * 时,究竟哪些名称会被导入?
这里就涉及一个特殊名称:__all__。
先看结论:
dir() 用于查看名称,而 __all__ 用于指定通配符导入时公开的名称列表。两者的用途不同。
创建一个自定义模块
为了理解它们的区别,我们自己创建一个模块。
进入之前的练习目录:
1 | cd ~/python-dir-demo |
创建 server_tools.py:
1 | def check_cpu(): |
这里定义了三个函数:
check_cpu():模拟检查 CPU。check_memory():模拟检查内存。check_disk():模拟检查磁盘。
此外,还有一个 version 变量,用来表示模块版本。
最重要的是这一行:
1 | __all__ = ["check_cpu", "check_disk"] |
它表示:当其他代码使用 from server_tools import * 导入这个模块时,指定导入的名称为 check_cpu 和 check_disk。
注意,__all__ 不是安全机制,也不会禁止别人通过 import server_tools 访问模块中的其他名称。
使用 dir() 查看模块
创建文件 test_dir.py:
1 | import server_tools |
运行:
1 | python test_dir.py |
输出中会包含:
1 | check_cpu |
上面仅列出部分关键名称。实际输出还会包含模块的其他属性,具体内容可能有所不同。
可以看到,虽然 __all__ 只列出了两个函数,但 dir(server_tools) 仍然可以看到 check_memory、version 和 __all__ 等名称。
这说明 __all__ 并不会限制 dir() 的输出。
使用 from…import * 导入
再创建一个文件 test_all.py:
1 | from server_tools import * |
运行:
1 | python test_all.py |
输出:
1 | 检查 CPU 使用率 |
如果我们改成下面这样:
1 | from server_tools import * |
运行后,就会出现类似下面的错误:
1 | NameError: name 'check_memory' is not defined |
为什么?
因为 server_tools.py 定义了 __all__,而 check_memory 不在这个列表中,所以它不会通过 from server_tools import * 自动导入到当前命名空间。
但如果使用普通导入:
1 | import server_tools |
仍然可以正常运行:
1 | 检查内存使用情况 |
这就体现了两者的区别。
| 对比项 | dir() | __all__ |
|---|---|---|
| 类型 | 内置函数 | 模块中可定义的特殊名称 |
| 主要作用 | 查看对象相关的名称 | 指定通配符导入时公开的名称 |
是否控制 dir() 输出 | 否 | 否 |
是否影响 from module import * | 不直接影响 | 会影响 |
| 是否禁止访问未列出的名称 | 否 | 否 |
实际开发中,通常更推荐明确导入需要使用的名称:
1 | from server_tools import check_cpu, check_disk |
或者:
1 | import server_tools |
这样代码的依赖关系更加清晰,也不容易因为名称冲突导致难以排查的问题。
Linux 运维实战:探索 shutil 模块
前面我们主要使用 math 模块来演示。
现在,把这些知识放到实际的 Linux 运维场景中。
假设你正在编写一个 Python 脚本,希望检查 Linux 服务器的磁盘空间,但暂时不熟悉 shutil 模块。
我们可以先查看模块有哪些名称。
创建文件 dir_shutil.py:
1 | import shutil |
运行:
1 | python dir_shutil.py |
输出会包含许多名称,例如:
1 | copy |
这些名称对应着不同的文件操作功能。
例如:
copy():复制文件。move():移动文件或目录。rmtree():删除目录树。make_archive():创建归档文件。disk_usage():查询文件系统的空间使用情况。
如果我们的目标是检查磁盘空间,那么可以进一步查询 disk_usage():
1 | import shutil |
查看帮助信息后,再编写一个简单的磁盘检查脚本。
创建 check_disk.py:
1 | import shutil |
运行:
1 | python check_disk.py |
输出示例:
1 | 检查路径:/ |
这里的容量仅为示例,实际输出取决于运行脚本的文件系统。
我们解释一下关键代码:
1 | total, used, free = shutil.disk_usage(path) |
shutil.disk_usage() 返回一个包含 total、used 和 free 三个字段的命名元组,分别表示文件系统的总容量、已使用容量和剩余容量,单位都是字节。
接下来:
1 | usage = used / total * 100 |
通过已使用容量除以总容量,再乘以 100,计算空间使用率。
最后使用格式化字符串:
1 | print(f"空间使用率:{usage:.2f}%") |
其中 :.2f 表示保留两位小数。
通过这个例子,我们就把 dir()、help() 和实际编程串联起来了。
如果以后需要检查其他系统信息,也可以采用相同思路:
- 导入相关模块。
- 使用
dir()查看名称。 - 找到可能需要的函数。
- 使用
help()查看用法。 - 编写一个最小示例并验证结果。
需要注意,磁盘空间检查只是一个基础示例。如果用于生产环境,还需要考虑挂载点选择、异常处理、阈值告警以及容器环境下的文件系统语义等问题。
使用 dir() 时需要注意什么?
学会 dir() 之后,也不要把它当作万能工具。
dir() 不等于完整 API 文档
dir() 主要列出对象相关的名称,但不会完整说明每个名称的参数、返回值、适用场景和使用限制。
因此,探索模块时,建议配合 help() 或官方文档使用。
dir() 不代表所有名称都能直接调用
例如:
1 | import math |
这里的 pi 是一个浮点数,而不是函数。
所以,不要看到 dir() 中有一个名称,就直接假设它可以加括号调用。
不要把 dir() 的输出写死在程序里
不同 Python 版本、不同对象以及不同实现可能会有不同的名称列表。
如果你正在编写自动化程序,不要依赖某次 dir() 输出的完整内容来判断功能是否存在。
如果确实需要检查某个属性,可以考虑:
1 | import math |
hasattr() 可以判断对象是否具有指定名称的属性。不过,如果你需要调用该属性,还应该根据实际情况确认它是否可调用,以及调用方式是否正确。
不要依赖 from module import * 来组织大型项目
虽然 __all__ 可以控制通配符导入时的公开名称,但在大型项目中,from module import * 往往会让代码的名称来源不够清楚。
相比之下,显式导入通常更容易阅读和维护:
1 | from shutil import disk_usage |
或者:
1 | import shutil |
选择哪种方式取决于项目规范和使用场景,但对于初学者,建议优先掌握明确导入的写法。
写在最后
今天,我们学习了 Python 中的 dir() 函数。
回顾一下:
dir(module)可以快速查看模块中的名称。dir(object)也可以用于探索字符串等其他对象。- 不带参数的
dir()会根据调用位置返回当前局部作用域中的名称;在模块顶层通常对应模块命名空间,在函数内部通常对应函数局部作用域。 globals()返回当前模块的全局命名空间字典,即使在函数内部调用也一样。locals()用于获取当前局部命名空间的映射。type()和callable()可以帮助我们判断对象的类型以及是否可调用。help()可以进一步查看函数和模块的帮助信息。__all__用于指定from module import *时公开的名称,它不会限制dir()的输出,也不是访问权限控制机制。
对于 Python 初学者来说,学习一门新语言,不必一开始就记住所有函数和模块。
更重要的是掌握探索工具:遇到陌生模块,先使用 dir() 找到可能需要的功能,再通过 help() 了解具体用法,最后编写代码验证结果。
这样,随着学习深入,你就能逐渐建立起独立探索 Python 标准库和第三方模块的能力。
下一篇,我们将继续学习 Python 标准库,看看有哪些值得掌握的常用模块,以及它们如何帮助我们减少重复开发工作,让 Linux 运维自动化变得更加高效。
