1. 引言:dir() 到底做了什么
dir() 是 Python 内置函数里最容易被低估的一个。初学者通常只记住一句话:「dir(obj) 返回对象的所有属性和方法」。但真正写代码时,你会发现它远不止于此:
- 在交互式解释器里直接敲
dir(),它返回的是当前作用域内的名字列表; - 敲
dir(1),它返回的是int类型上所有可访问的属性名; - 敲
dir(MyClass),它返回的是类对象自身的属性,而不是实例属性; - 敲
dir(instance),它返回的列表里却包含了类上定义的属性。
同一个函数,四种输入,四种语义。本文从行为表现 → 查找规则 → CPython 源码实现 → 实战用法 → 常见坑 五个层次,把 dir() 彻底拆开讲清楚。
2. 函数签名与基本行为
dir() 的签名非常简单:
python
dir([object])
- 不传参数:返回当前局部作用域中的名字列表;
- 传入参数:返回该对象的属性名列表。
返回值类型始终是 list[str],并且按字母顺序排序 。这一点很重要------排序是 dir() 的契约之一,而不是巧合。
python
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> dir(42)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', ...]
>>> dir("abc")[:5]
['__add__', '__class__', '__contains__', '__delattr__', '__dir__']
注意 dir(42) 和 dir("abc") 返回的列表长度不同,因为 int 和 str 各自实现了不同的特殊方法。这说明 dir() 的结果是动态计算出来的,而不是某个静态表。
3. 四种调用形态的语义差异
3.1 无参数:返回当前作用域名字
无参调用时,dir() 等价于查看「当前命名空间里有哪些名字」。在模块顶层,它返回模块的全局名字;在函数内部,它返回局部变量名。
python
def demo():
x = 1
y = 2
print(dir())
# ['x', 'y'] ------ 只包含局部变量,不含全局名
demo()
这里有个容易忽略的细节:函数内部的 dir() 不会 包含全局变量和内置名字,因为它读取的是 locals()。
3.2 传入实例:合并实例字典与类属性
python
class Point:
kind = "2d"
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(dir(p))
# ['__class__', '__delattr__', ..., 'kind', 'x', 'y']
可以看到,dir(p) 同时包含了:
- 实例字典里的
x、y; - 类属性
kind; - 从
object继承来的大量特殊方法。
也就是说,dir(instance) 走的是属性查找链 ,而不是简单的 instance.__dict__.keys()。
3.3 传入类:只返回类对象自身的属性
python
print(dir(Point))
# ['__class__', ..., 'kind', '__init__', ...]
# 注意:没有 'x'、'y'
类对象本身也是一个对象(type 的实例),所以 dir(Point) 返回的是「类对象」的属性,包括类属性、方法、以及 type 提供的元属性。实例属性 x、y 只存在于实例上,自然不会出现。
3.4 传入模块:返回模块命名空间
python
import math
print(dir(math)[:5])
# ['__doc__', '__file__', '__loader__', '__name__', '__package__']
模块的 dir() 基本就是模块 __dict__ 的键集合,再加上少量特殊名字。
4. 核心机制:dir 协议
dir() 之所以能对任意对象工作,是因为它依赖一个协议方法 :__dir__。
规则如下:
- 如果对象定义了
__dir__,dir(obj)会调用它,并把返回值排序后返回; - 如果没有定义,
dir()会走默认逻辑:收集对象的__dict__、类型上的属性、以及基类链上的属性。
这意味着你可以完全自定义 dir() 的输出:
python
class Dynamic:
def __dir__(self):
return ["alpha", "beta", "gamma"]
d = Dynamic()
print(dir(d))
# ['alpha', 'beta', 'gamma']
注意:__dir__ 的返回值必须是可迭代的字符串序列 ,否则会抛 TypeError。而且 dir() 会对结果排序,所以你在 __dir__ 里返回的顺序不会保留。
4.1 一个实用的自定义场景
在 ORM 或配置对象里,属性是动态生成的,IDE 无法静态推断。此时实现 __dir__ 可以让自动补全正常工作:
python
class Config:
_fields = ["host", "port", "user"]
def __getattr__(self, name):
if name in self._fields:
return f"<{name}>"
raise AttributeError(name)
def __dir__(self):
return list(self._fields) + ["_fields"]
cfg = Config()
print(dir(cfg))
# ['_fields', 'host', 'port', 'user']
这样在 IPython 里敲 cfg. 再按 Tab,就能看到 host、port、user 三个候选。
5. CPython 源码视角:dir() 是怎么实现的
dir() 在 CPython 中由 builtin_dir 实现(位于 Python/bltinmodule.c)。其逻辑可以概括为:
c
static PyObject *
builtin_dir(PyObject *self, PyObject *args)
{
PyObject *arg = NULL;
if (!PyArg_UnpackTuple(args, "dir", 0, 1, &arg))
return NULL;
if (arg == NULL) {
// 无参数:返回当前局部作用域的名字
PyObject *locals = PyEval_GetLocals();
...
return PySequence_List(locals);
}
// 有参数:调用 PyObject_Dir
return PyObject_Dir(arg);
}
关键在 PyObject_Dir(Objects/object.c),它的流程是:
- 先尝试获取
type(obj)->tp_dir,如果类型实现了tp_dir槽位,直接调用; - 否则检查对象是否有
__dir__方法,有则调用; - 都没有时,走
_dir_object默认实现:合并__dict__、类型属性、基类属性; - 最后对结果调用
PyList_Sort排序。
用流程图表示:
#mermaid-svg-5KpFCmFLBL2utSC7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5KpFCmFLBL2utSC7 .error-icon{fill:#552222;}#mermaid-svg-5KpFCmFLBL2utSC7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5KpFCmFLBL2utSC7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5KpFCmFLBL2utSC7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5KpFCmFLBL2utSC7 .marker.cross{stroke:#333333;}#mermaid-svg-5KpFCmFLBL2utSC7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5KpFCmFLBL2utSC7 p{margin:0;}#mermaid-svg-5KpFCmFLBL2utSC7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 .cluster-label text{fill:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 .cluster-label span{color:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 .cluster-label span p{background-color:transparent;}#mermaid-svg-5KpFCmFLBL2utSC7 .label text,#mermaid-svg-5KpFCmFLBL2utSC7 span{fill:#333;color:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 .node rect,#mermaid-svg-5KpFCmFLBL2utSC7 .node circle,#mermaid-svg-5KpFCmFLBL2utSC7 .node ellipse,#mermaid-svg-5KpFCmFLBL2utSC7 .node polygon,#mermaid-svg-5KpFCmFLBL2utSC7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5KpFCmFLBL2utSC7 .rough-node .label text,#mermaid-svg-5KpFCmFLBL2utSC7 .node .label text,#mermaid-svg-5KpFCmFLBL2utSC7 .image-shape .label,#mermaid-svg-5KpFCmFLBL2utSC7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-5KpFCmFLBL2utSC7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5KpFCmFLBL2utSC7 .rough-node .label,#mermaid-svg-5KpFCmFLBL2utSC7 .node .label,#mermaid-svg-5KpFCmFLBL2utSC7 .image-shape .label,#mermaid-svg-5KpFCmFLBL2utSC7 .icon-shape .label{text-align:center;}#mermaid-svg-5KpFCmFLBL2utSC7 .node.clickable{cursor:pointer;}#mermaid-svg-5KpFCmFLBL2utSC7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5KpFCmFLBL2utSC7 .arrowheadPath{fill:#333333;}#mermaid-svg-5KpFCmFLBL2utSC7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5KpFCmFLBL2utSC7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5KpFCmFLBL2utSC7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5KpFCmFLBL2utSC7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5KpFCmFLBL2utSC7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5KpFCmFLBL2utSC7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5KpFCmFLBL2utSC7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5KpFCmFLBL2utSC7 .cluster text{fill:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 .cluster span{color:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5KpFCmFLBL2utSC7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5KpFCmFLBL2utSC7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-5KpFCmFLBL2utSC7 .icon-shape,#mermaid-svg-5KpFCmFLBL2utSC7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5KpFCmFLBL2utSC7 .icon-shape p,#mermaid-svg-5KpFCmFLBL2utSC7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5KpFCmFLBL2utSC7 .icon-shape .label rect,#mermaid-svg-5KpFCmFLBL2utSC7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5KpFCmFLBL2utSC7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5KpFCmFLBL2utSC7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5KpFCmFLBL2utSC7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
是
否
dir(obj) 调用
是否传入参数
读取当前 locals 并转 list
PyObject_Dir(obj)
类型有 tp_dir 槽位
调用 tp_dir
对象有 dir 方法
调用 dir
默认实现: 合并 dict / 类型属性 / 基类属性
排序结果
返回 liststr
这里有个值得注意的点:排序发生在最后一步 ,所以无论 __dir__ 返回什么顺序,最终都是字母序。这也是为什么 dir() 的结果看起来总是「整齐」的。
6. dir() 与 vars()、dict 的区别
很多人把这三个混为一谈,实际上它们的语义差别很大:
| 对比项 | dir(obj) |
vars(obj) |
obj.__dict__ |
|---|---|---|---|
| 返回类型 | list[str] |
dict |
dict |
| 是否包含继承属性 | 是 | 否 | 否 |
| 是否包含类属性 | 是 | 否 | 否 |
| 是否排序 | 是 | 否 | 否 |
| 无参行为 | 当前作用域名字 | 当前局部变量字典 | 不适用 |
python
class A:
cls_attr = 1
def __init__(self):
self.inst_attr = 2
a = A()
print(dir(a)) # 包含 cls_attr、inst_attr 及大量特殊方法
print(vars(a)) # {'inst_attr': 2}
print(a.__dict__) # {'inst_attr': 2}
结论:想看「有哪些名字」用 dir();想看「实例自己存了什么」用 vars() 或 __dict__。
7. 实战用法
7.1 交互式探索未知对象
拿到一个第三方库对象,第一反应就是 dir():
python
import requests
resp = requests.get("https://example.com")
print([n for n in dir(resp) if not n.startswith("_")])
# ['apparent_encoding', 'close', 'connection', 'content', 'cookies', ...]
过滤掉下划线开头的名字,能快速看到公开 API。
7.2 实现简易的自动补全
python
def complete(obj, prefix=""):
return [n for n in dir(obj) if n.startswith(prefix)]
print(complete("hello", "is"))
# ['isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', ...]
7.3 调试时检查对象能力
python
def describe(obj):
public = [n for n in dir(obj) if not n.startswith("_")]
print(f"{type(obj).__name__} 提供 {len(public)} 个公开成员")
for name in public:
attr = getattr(obj, name)
print(f" {name}: {type(attr).__name__}")
describe([1, 2, 3])
7.4 配合 getattr 做动态调用
python
class Handler:
def handle_create(self): return "created"
def handle_delete(self): return "deleted"
h = Handler()
action = "create"
method_name = f"handle_{action}"
if method_name in dir(h):
print(getattr(h, method_name)())
# created
注意:用 dir() 做存在性判断并不严谨,更推荐 hasattr(),因为 dir() 可能被 __dir__ 定制而失真。
8. 常见坑与注意事项
8.1 dir() 的结果不保证完整
如果类实现了自定义 __dir__,返回的列表可能遗漏 真实存在的属性。所以 dir() 适合探索,不适合做严格的成员判断。
python
class Sneaky:
real_attr = 1
def __dir__(self):
return []
s = Sneaky()
print(dir(s)) # []
print(hasattr(s, "real_attr")) # True
8.2 无参 dir() 在函数内只返回局部变量
python
GLOBAL = 1
def f():
local = 2
print(dir()) # ['local'],不含 GLOBAL
f()
8.3 排序是字符串序,不是「定义顺序」
dir() 按字母序排序,所以 __init__ 会排在 __class__ 之后,这与源码中的定义顺序无关。如果你需要定义顺序,应该用 __dict__(Python 3.7+ 保证插入顺序)。
8.4 对性能敏感场景不要滥用
dir() 每次调用都会构建列表并排序,在热路径里频繁调用会有开销。缓存结果或改用 hasattr() 更合适。
9. 总结
把 dir() 的要点浓缩成一张表:
| 维度 | 结论 |
|---|---|
| 本质 | 内置函数,依赖 __dir__ 协议 |
| 无参 | 返回当前局部作用域名字列表 |
| 有参 | 返回对象可访问属性名列表,含继承属性 |
| 排序 | 始终按字母序 |
| 可定制 | 实现 __dir__ 即可自定义输出 |
| 与 vars 区别 | dir 看名字,vars 看实例字典 |
| 适用场景 | 交互探索、自动补全、调试 |
| 不适用 | 严格成员判断、性能热路径 |
一句话记住:dir() 是「对象能提供哪些名字」的视图,而不是「对象实际存了什么」的清单。 理解了 __dir__ 协议和 CPython 的 PyObject_Dir 实现,你就能在需要时既用好它,也能定制它。