Python getattr_static:只是检查属性,为什么 property 里的代码已经执行了

前天 3阅读

给对象生成字段说明时,程序只想确认 total 是否存在,于是先调用 hasattr,再调用 getattr 展示值。结果查询计数多了两次,原本只读的检查还触发了计算。属性访问并不总是从字典拿出一个值;property 的取值函数可以做任何普通函数能做的事情。文档工具、调试面板和对象检查器应先明确自己需要的是属性定义,还是实际取值。

inspect.getattr_static 适合被动检查。它绕过描述符协议及动态属性查找,可能交回 property 对象,而不是函数算出的结果。下面故意把副作用缩小成内存中的计数,不读文件也不联网。将完整代码保存为 demo.py,运行 python demo.py,即可比较两条路径。

Python getattr_static:只是检查属性,为什么 property 里的代码已经执行了

AI生成概念插图:放大镜在封闭盒外检查结构,表示观察属性定义时不启动取值过程;不是软件界面或运行截图。

from inspect import getattr_static

class Report:
    def __init__(self):
        self.reads = 0

    @property
    def total(self):
        self.reads += 1
        return 42

    def __getattr__(self, name):
        if name == "virtual":
            return "generated"
        raise AttributeError(name)

r = Report()
raw = getattr_static(r, "total")
assert isinstance(raw, property)
assert r.reads == 0
print("static:", type(raw).__name__, r.reads)
assert hasattr(r, "total") is True
assert r.reads == 1
value = getattr(r, "total")
assert (value, r.reads) == (42, 2)
print("dynamic:", value, r.reads)

missing = object()
assert getattr(r, "virtual") == "generated"
assert getattr_static(r, "virtual", missing) is missing
print("virtual: dynamic only")

第一行证明检查没有读取业务值

第一行输出 static: property 0。拿到的 raw 是类上定义的属性对象,计数仍为零。随后 hasattr 确认名称存在时执行了一次 getter,getattr 再读取时又执行一次,所以第二行输出 dynamic: 42 2。把 hasattr 放在 getattr 前面,并不能让读取变得更安全,也不能让两次操作自动共用结果。

如果业务最终确实需要数值,应主动决定何时执行 getter,并把失败作为真实读取错误处理。若只要列出接口类型,则可以把 property 标成“计算属性”,暂不解析。不要拿到 raw 后为了展示漂亮的值,又立即调用它的 __get__,否则刚刚建立的被动检查边界就消失了。

没有找到,不一定是运行时没有

virtual 只在 __getattr__ 里动态生成,普通读取能得到 generated,静态读取则得到 missing 标记。这里的结论是“静态结构中未找到”,不能写成“对象绝不支持这个名字”。因此两个函数不是可随手互换的版本:要回答的问题不同,合理的输出也会不同。

使用独立的 object 作为缺失标记,是为了区分缺失和属性值本来就是 None。检查时用 is 比较标记,不要把真假判断混进来,因为零、空列表和空字符串也都可能是合法值。若调用时不提供默认值,静态查找失败会抛出 AttributeError,可按检查器的输出格式转换成明确状态。

官方文档还提醒,被其他成员遮蔽的实例 __dict__ 会影响实例字段查找;槽位及某些扩展类型的描述符也可能直接作为结果返回。不要承诺静态检查能完整模拟运行时行为。为复杂对象保留“定义存在但未求值”和“静态未发现”两种展示,比擅自把未知解释为不存在更准确。

落地时可给检查器准备三个样本:普通存储字段、带计数的 property、通过 __getattr__ 生成的名称。分别核对返回对象和副作用次数,而不是只比较界面文字。若检查器还自行调用 repr、其他方法或格式化钩子,那些步骤也需要单独检查;静态查找只约束这一项操作。

资料核对日期:2026年10月2日(北京时间)。示例在本地CPython 3.12.14实际运行并通过断言,结果对应文中固定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。