Python 3.12 Magic Method -__dir__(self)__dir__是 Python 中用于定义对象属性列表的核心魔术方法。当内置函数dir()作用于一个对象时Python 会尝试调用该对象的__dir__方法返回一个属性名称列表。该方法使得自定义类可以控制dir()的输出从而隐藏或动态生成属性提高代码的可读性和封装性。本文将详细解析其定义、底层机制、设计原则并通过多个示例逐行演示如何正确实现。1. 定义与签名def__dir__(self)-list:...参数self当前对象实例。返回值必须返回一个可迭代对象通常是列表包含属性名字符串。如果返回其他类型Python 会尝试将其转换为列表但最好直接返回列表。调用时机显式调用dir(obj)。在交互式解释器中当输入对象名并回车时会调用dir()显示属性。某些调试工具和 IDE 也会利用dir()来展示对象成员。2. 用途与典型场景隐藏内部属性在__dir__中过滤掉以_开头的属性使dir()只显示“公开”接口。动态属性列表当属性是动态生成的如通过__getattr____dir__可以列出这些动态属性让用户知道有哪些可用属性。简化输出对于复杂对象如 ORM 模型dir()默认会显示大量不重要的属性通过__dir__可以精简列表只显示常用属性。与__getattr__配合如果类实现了__getattr__通常也应实现__dir__来告知动态属性的存在否则dir()不会显示它们。3. 底层实现机制在 Python/C API 层面dir()函数的实现位于Python/bltinmodule.c大致流程如下如果对象有__dir__方法则调用它并返回其结果。否则使用默认的dir()实现收集对象的所有属性包括从__dict__或__slots__中获取实例属性从类和基类中获取类属性包含__dict__和__class__等特殊属性对结果进行排序返回一个列表。因此实现__dir__可以让对象完全控制dir()的输出覆盖默认的收集行为。4. 设计原则与最佳实践返回列表或可迭代对象通常返回一个列表但也可以返回元组或其他可迭代对象Python 会将其转换为列表。包含必要属性即使你想隐藏某些属性也应该包含一些重要属性如__dict__、__class__等否则dir()的结果可能让用户困惑。保持一致性__dir__返回的列表应与对象实际可用的属性一致尤其是与__getattr__动态生成的属性保持一致。避免性能问题__dir__可能被频繁调用如调试时应避免在其中进行昂贵操作。与继承协作在子类中实现__dir__时通常应调用父类的__dir__并合并结果以保留父类的属性。5. 示例与逐行解析示例 1隐藏内部属性以下划线开头的属性classUser:def__init__(self,name,email):self.namename self.emailemail self._passwordsecretdef__dir__(self):# 获取默认的属性列表default_dirsuper().__dir__()# 过滤掉以下划线开头的属性return[attrforattrindefault_dirifnotattr.startswith(_)]逐行解析行代码解释1-6类定义定义了一个用户类包含公开属性和一个私有属性_password。7-10__dir__调用父类的__dir__获取默认属性列表然后过滤掉以下划线开头的属性返回新列表。为什么这样写通过过滤只显示“公开”属性使dir()更简洁保护内部实现细节。调用super().__dir__()保留了父类object提供的内置属性如__class__但通过过滤去掉了它们。如果你想保留某些内置属性可以调整过滤条件。验证uUser(Alice,aliceexample.com)print(dir(u))# 不会显示 _password 和 __class__ 等内置属性运行结果[email, name]示例 2动态生成属性列表与__getattr__协同classDynamicAttrs:def__init__(self):self._data{name:Alice,age:30}def__getattr__(self,name):# 动态返回 _data 中的属性ifnameinself._data:returnself._data[name]raiseAttributeError(f{type(self).__name__} object has no attribute {name})def__dir__(self):# 返回默认属性 动态属性default_dirsuper().__dir__()dynamic_dirlist(self._data.keys())returnsorted(set(default_dirdynamic_dir))逐行解析行代码解释1-5__init__内部存储一个字典_data模拟动态属性。6-11__getattr__当访问不存在的属性时从_data中查找并返回。12-16__dir__获取默认属性列表加上动态属性名去重排序后返回。这样dir()会显示这些动态属性。为什么这样写如果没有__dir__dir(d)不会显示name和age用户可能不知道存在这些动态属性。通过__dir__告知提升了可用性。使用set去重然后排序使输出有序。验证dDynamicAttrs()print(dir(d))# 包含 name, age 以及默认属性print(d.name)# Alice运行结果[ __class__, __delattr__, __dict__, __dir__, __doc__, __eq__, __format__, __ge__, __getattr__, __getattribute__, __getstate__, __gt__, __hash__, __init__, __init_subclass__, __le__, __lt__, __module__, __ne__, __new__, __reduce__, __reduce_ex__, __repr__, __setattr__, __sizeof__, __str__, __subclasshook__, __weakref__, _data, age, name] Alice示例 3精简输出只显示常用属性classDatabaseConnection:def__init__(self,host,port,user):self.hosthost self.portport self.useruser self._closedFalseself._internal_id12345def__dir__(self):# 只返回对用户重要的属性return[host,port,user,close]defclose(self):self._closedTrue逐行解析行代码解释1-7类定义包含公开属性和内部属性。8-10__dir__直接返回一个自定义列表只包含对用户重要的属性和方法。11-13close方法示例方法。为什么这样写对于复杂对象dir()默认会显示大量内部属性和方法可能让用户困惑。通过__dir__精简输出仅暴露对外接口提高可读性。注意即使不在__dir__中列出用户仍然可以通过其他方式如conn.__dict__访问隐藏属性但__dir__主要用于交互式帮助和文档生成。验证connDatabaseConnection(localhost,5432,admin)print(dir(conn))# [close, host, port, user]运行结果[close, host, port, user]示例 4继承与合并保留父类的属性classBase:def__init__(self):self.base_attrbasedefbase_method(self):passdef__dir__(self):return[base_attr,base_method]classDerived(Base):def__init__(self):super().__init__()self.derived_attrderiveddefderived_method(self):passdef__dir__(self):# 合并父类和子类的属性列表parent_dirsuper().__dir__()own_dir[derived_attr,derived_method]returnsorted(parent_dirown_dir)逐行解析行代码解释1-9Base类定义了基础属性和方法并自定义__dir__。10-20Derived类继承Base添加自己的属性并重写__dir__。18-20合并通过super().__dir__()获取父类列表加上自己的列表去重排序后返回。为什么这样写在子类中实现__dir__时应调用父类的__dir__并合并结果以保证父类定义的接口也被列出。否则dir()可能只显示子类属性而隐藏了继承来的重要属性。验证# dir(d) 包含了父类和子类的所有指定属性。dDerived()print(dir(d))# [base_attr, base_method, derived_attr, derived_method]运行结果[base_attr, base_method, derived_attr, derived_method]6. 注意事项与陷阱不要修改对象状态__dir__应是无副作用的不应改变对象内部数据。性能__dir__可能被频繁调用如交互式环境应避免在其中进行复杂计算或 IO 操作。与__getattr__的一致性如果类实现了__getattr__来动态生成属性通常也应实现__dir__来列出这些动态属性否则用户无法知道它们的存在。返回的列表不一定要包含所有实际属性__dir__返回的列表是“建议”的用户仍可通过hasattr、getattr或直接访问__dict__获取未列出的属性。但__dir__的主要用途是提供友好的帮助信息。处理__slots__如果类使用了__slots__则默认的__dir__会包含这些槽名。自定义__dir__时需注意合并这些槽名。7. 总结特性说明角色定义dir()返回的属性列表签名__dir__(self) - list返回值字符串列表代表属性名称调用时机dir(obj)底层由dir()函数调用覆盖默认的属性收集逻辑最佳实践返回可迭代对象与__getattr__协同在子类中合并父类结果避免副作用掌握__dir__可以让你精细控制对象的“可见”属性提升交互式体验和代码的封装性。通过合理实现你的类可以更友好地展示其公共接口隐藏内部细节让用户更容易上手。如果在学习过程中遇到问题欢迎在评论区留言讨论!