Python技术迷

Python 中 help() 函数和 dir() 函数有什么作用?

线上脚本报了个很烦的错:

AttributeError: 'OrderClient' object has no attribute 'sync_order'

这种问题我一般不急着翻文档。尤其是接了别人写的 Python 包,目录里一堆类,一堆方法名,文档还不一定跟代码同步。先把对象拎出来看一眼,比猜半天强。

这里就会用到两个老工具:dir() 和 help()。

dir() 干的事很直接:把一个对象身上能访问的名字列出来。

比如我拿到一个订单客户端对象:

classOrderClient:
def__init__(self, env):
        self.env = env
        self.timeout = 3

defpush_order(self, order_no):
"""推送订单到外部系统"""
returnf"{order_no} pushed"

defquery_order(self, order_no):
"""查询订单状态"""
return {"order_no": order_no, "status": "WAIT_PAY"}

client = OrderClient("prod")

print(dir(client))

输出会很长,里面有一堆双下划线方法:

['__class__', '__delattr__', '__dict__', ..., 'env', 'push_order', 'query_order', 'timeout']

别被这些东西吓到。你真正要看的,一般是后面几个业务字段和业务方法。

我现场排查时,经常会这么过滤一下:

names = [x for x in dir(client) ifnot x.startswith("_")]
print(names)

输出就清爽多了:

['env', 'push_order', 'query_order', 'timeout']

这时候就很明显了,代码里调用的是 sync_order,对象上压根没有这个方法,只有 push_order 和 query_order。

这类问题,dir() 比肉眼翻代码快。尤其对象是从第三方 SDK、动态工厂、配置加载出来的,类在哪都不一定好找。先 dir() 一下,至少知道它现在到底长什么样。

不过 dir() 也别迷信。

它告诉你“有什么名字”,但不告诉你“这个名字该怎么用”。比如你看到了 push_order,它要传几个参数,返回什么,异常怎么处理,dir() 不管。

这时候就该 help() 上场了。

help(client.push_order)

如果代码里 docstring 写得还行,会看到类似内容:

push_order(order_no)
    推送订单到外部系统

help() 的作用就是把对象的说明信息打印出来。函数、类、模块都能看。

看函数:

defclean_mobile(raw):
"""
    清洗手机号:
    - 去掉空格和短横线
    - 非 11 位直接返回 None
    """

    text = raw.replace(" ", "").replace("-", "")
if len(text) != 11:
returnNone
return text

help(clean_mobile)

看类:

help(OrderClient)

看模块也行:

import json
help(json)

不过模块的 help 输出通常很长,我一般不这么看,除非是标准库临时忘了参数。

比如处理一批接口日志,要把 JSON 字符串转成字典,突然记不清 json.loads 和 json.load 的区别,直接:

import json

help(json.loads)
help(json.load)

你会发现,一个吃字符串,一个吃文件对象。

这比去搜索引擎里翻半天“Python json load loads 区别”要干净。尤其线上机器不能随便访问外网的时候,这两个函数就是本地文档。

再说一个容易踩的点。

dir() 看到的方法,不一定都适合你直接调用。比如这种:

print([x for x in dir(client) if"order"in x])

可能输出:

['push_order', 'query_order']

这个可以参考。

但如果输出里有:

['_build_order_payload', '_sign_order_request', 'push_order']

带下划线的,一般先别碰。不是不能调用,是作者大概率不希望你直接用。那种方法通常是内部拼参数、签名、封装请求的,业务代码直接调,后面升级 SDK 很容易炸。

我见过有人为了图省事,直接调用 _build_xxx(),当时确实跑通了。过两周依赖包一升级,字段名变了,调用方全挂。这个锅还不好甩,因为人家从名字上就提醒你了:内部方法。

还有一种情况,dir() 看着没问题,但调用还是报错。

print("push_order"in dir(client))
# True

client.push_order()

结果:

TypeError: push_order() missing 1 required positional argument: 'order_no'

这时候别猜参数,直接:

help(client.push_order)

或者更硬一点,用 inspect 看签名:

import inspect

print(inspect.signature(client.push_order))

输出:

(order_no)

这个在排查封装过好几层的代码时很好用。help() 看说明,inspect.signature() 看参数,dir() 看名字。三个放一起,基本能摸清一个对象七八成。

我平时还会写个小函数,专门看对象里有哪些能调用的方法:

defdump_public_methods(obj):
    rows = []
for name in dir(obj):
if name.startswith("_"):
continue

        value = getattr(obj, name)
if callable(value):
            rows.append(name)

return rows

print(dump_public_methods(client))

输出:

['push_order', 'query_order']

这段代码不复杂,但很实用。接手一个陌生对象时,先看它暴露了哪些公开方法。不要上来就全局搜索,不然你会被一堆同名函数、继承类、测试代码带偏。

help() 也有个前提:代码作者得写说明。

如果函数是这么写的:

deffix(row):
return row

那 help(fix) 也救不了你。最多告诉你有个 fix(row),至于修什么,怎么修,谁也不知道。

所以写 Python 时,我对 docstring 的要求不高,但关键函数必须写两句。不是为了显得规范,是为了三个月后自己少骂自己。

比如这种就够了:

defmerge_refund_rows(rows):
"""
    合并退款明细。
    同一个 refund_no 只保留最后一次状态,用于对账导出前的数据压平。
    """

    merged = {}
for row in rows:
        merged[row["refund_no"]] = row
return list(merged.values())

后面别人 help(merge_refund_rows),至少知道这玩意不是随便合并列表,而是按退款单号覆盖。

dir() 适合“我不知道它有什么”。

help() 适合“我知道它有这个东西,但不知道怎么用”。

这两个函数不高级,也不新,但在现场排查里很顺手。Python 的很多问题,不是语法难,是对象绕来绕去,看不清。先把对象摊开,再看说明,比靠感觉改代码靠谱多了。