深入解析 Python 的 dir():从用法到 CPython 源码实现

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__。

规则如下:

  1. 如果对象定义了 __dir__,dir(obj) 会调用它,并把返回值排序后返回;
  2. 如果没有定义,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),它的流程是:

  1. 先尝试获取 type(obj)->tp_dir,如果类型实现了 tp_dir 槽位,直接调用;
  2. 否则检查对象是否有 __dir__ 方法,有则调用;
  3. 都没有时,走 _dir_object 默认实现:合并 __dict__、类型属性、基类属性;
  4. 最后对结果调用 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 实现,你就能在需要时既用好它,也能定制它。

相关推荐
重生之我在番茄自学网安拯救世界1 小时前
AI 开发者日报 第1期(本周精选)| 插件市场投毒、智能体搞出“官僚饥荒村”,十年前的 CNN 打赢大模型
人工智能·网络安全·大模型·ai编程·智能体
涛声依旧-底层原理研究所1 小时前
拆解 Cordis 底座:Agent 是如何被「组合」出来的
人工智能·架构
nice_lcj5202 小时前
深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)
java·开发语言·后端·面试
秦先生在广东2 小时前
记忆不再是黑盒
人工智能
秦先生在广东2 小时前
49个AI智能体接管你的游戏开发
人工智能
骑着蜗牛撵大象3272 小时前
2026 后端 AI 工程化:Spring AI 管调用、MCP 管工具、Agent 内嵌管自治
java·人工智能·后端·spring·ai·可观测性·mcp
秦先生在广东2 小时前
26款模型安全提示词全开源
人工智能
smilejingwei2 小时前
TRAE 插件上新 | SQLazy:让 AI 写出可信复杂 SQL
数据库·人工智能·sql