原文:
towardsdatascience.com/dont-let-the-python-dir-function-trick-you-3c42abbdb4d9
PYTHON 编程
这真的是全部吗?dir() 函数并不能保证显示所有内容!图片由 Cristiano Pinto 在 Unsplash 提供
这就是 Python 的内置 dir() 函数:
dir() 函数的帮助信息。图片由作者提供
简而言之,当用于一个对象,比如 obj 时,该函数返回其属性名称……或者技术上,一些属性名称——这 一些 确实有很大区别。
如果你搜索 互联网,你会找到很多关于 dir() 这个问题的疑问。在 StackOverflow 上,你会找到关于各种 Python 对象的这个问题,例如 Outlook 消息,json 对象,解析的 XML 对象,或 scipy.sparse 对象。显然,人们期望 dir() 返回所有属性。在研究这个主题之前,我也期望如此——所以我理解这种困惑。
即使你真的阅读了函数的文档字符串并注意到了 “some of attributes” 这个短语,这也不助于找到所有属性。
本文讨论了 dir() 函数这个奇怪的特性。我们将讨论为什么它不会列出对象的全部属性,以及如果你想要这样做,应该怎么做。这样的函数在调试期间尤其有用,当你需要理解对象的行为时。然而,本文最重要的目的可能是帮助你避免一个错误,即认为 dir() 返回的属性名称是完整的。
关于 dir()
dir() 函数是一个广泛使用的工具,用于检查 Python 对象,为开发者提供了一种快速列出与对象关联的属性和方法名称的方法。¹ 因此,当你试图理解 Python 对象的复杂性时,它是一个非常实用的函数。我一直在广泛地使用它,大多数情况下是在交互式会话中,我想大多数 Python 程序员也会这样做。
dir() 的目的是尽可能返回对象的属性名称——但它并不保证返回 所有 属性。这就是函数文档字符串中 “some of” 这个短语的意思。
首先,在使用此函数时请记住这一点。其次,如果你确实需要找到所有属性的名称,dir() 并不是你的朋友。在下一节中,我们将实现一个能满足你需求的函数。
查找所有属性
在本节中,我们将实现 get_all_attributes() 函数,该函数结合了三种内省技术,以提供一个对象属性的全面概述:
-
内置的
dir()函数。 -
对象的
__dict__属性,如果有的话。__dict__通常包含对象的所有可写属性。 -
Python 标准库中的
inspect模块。我们将使用inspect.getmembers()函数,因为它提供了对象的全部成员的完整列表,不仅限于__dict__中的成员,因此它包括动态添加的属性、继承的属性等等。 -
元类信息。我们将从对象的元类(即类的类)中提取属性,这可以提供对类级别方法和属性的见解。
import inspect
from typing import Any, Union
def get_all_attributes(
obj: Any,
unique: bool = True,
) -> Union[list[str], dict[str, list[str]]]:
attrs = {}
attrs["dir"] = dir(obj)
if hasattr(obj, '__dict__'):
attrs["__dict__"] = list(obj.__dict__.keys())
attrs["inspect.getmembers"] = [
name
for name, _ in inspect.getmembers(obj)
]
metaclass = type(obj)
attrs["metaclass"] = dir(metaclass)
if unique:
attrs = {
element
for sublist in attrs.values()
for element in sublist
}
return attrs
让我们分析这个函数。首先,该函数的目标是列出 Python 对象的所有属性,包括来自 dir() 函数和对象的 __dict__ 属性、由 inspect.getmembers() 函数揭示的属性以及来自其元类的属性。
该函数有两个参数:obj,即分析的对象;以及 unique,一个布尔标志。当 unique 为 True 时,该函数返回一个包含 obj 所有属性名称的唯一集合的列表,这是我们通常期望此类函数的行为。当它是 False 时,该函数返回一个字典,其中每个键指向不同的属性来源;这些列表将部分重叠。因此,如果你对获取属性感兴趣(这是默认行为),请使用 unique=True;如果你想要比较不同属性来源,请使用 unique=False。
示例
你可以使用 get_all_attributes() 函数来获取任何对象的属性。通常,它的输出与 dir() 的输出完全重叠,但对于某些对象,则不会。我想在这里展示一个来自 Python 标准库的突出例子,其中 get_all_attributes() 返回的属性比 dir() 多出几倍:enum.Enum 类及其值。
首先,让我们定义一个非常简单的枚举类:
>>> from enum import Enum
>>> class X(Enum):
... X1 = 1
... X2 = 2
... X3 = 3
从 enum.Enum 继承的 X 类提供了一个具有三个可能值的枚举:
>>> X.__members__
mappingproxy({'X1': <X.X1: 1>, 'X2': <X.X2: 2>, 'X3': <X.X3: 3>})
首先,让我们看看类的属性。这是 dir() 返回的内容:
>>> dir_X = dir(X)
>>> len(dir_X)
14
>>> dir_X # doctest: +NORMALIZE_WHITESPACE
['X1', 'X2', 'X3', '__class__', '__contains__',
'__doc__', '__getitem__', '__init_subclass__',
'__iter__', '__len__', '__members__', '__module__',
'__name__', '__qualname__']
这是 get_all_attributes() 的实际应用:
>>> get_all_X = get_all_attributes(X)
>>> len(get_all_X)
76
>>> get_all_X # doctest: +NORMALIZE_WHITESPACE
{'_value_repr_', 'mro', '__eq__', '__dict__',
'_value2member_map_', '__dir__', '__getattribute__',
'__itemsize__', '__reduce__', '__subclasshook__',
'_check_for_existing_members_', '__annotations__',
'__str__', '__class__', '__new__', '__base__',
'__abstractmethods__', '__bool__', '__delattr__',
'_generate_next_value_', '__bases__', '__ne__',
'__prepare__', '__call__', '__flags__',
'__reduce_ex__', 'X3', '_use_args_', 'value',
'__hash__', '__members__', '__sizeof__', '__setattr__',
'_get_mixins_', '__iter__', '__name__', '__init__',
'_member_names_', '__module__', '_create_', '__doc__',
'__basicsize__', '__format__', '__len__',
'__weakrefoffset__', '__reversed__', '__ge__',
'__init_subclass__', '__subclasscheck__', '_convert_',
'__mro__', '__or__', '_find_data_type_', '_member_type_',
'_new_member_', '__le__', '__contains__', 'X1', '__gt__',
'name', '__instancecheck__', '__repr__', '__text_signature__',
'_unhashable_values_', '__qualname__', '__getattr__',
'__getstate__', '_member_map_', '__subclasses__',
'__dictoffset__', '__lt__', 'X2', '__getitem__',
'_find_data_repr_', '__ror__', '_find_new_'}
哇,这之间的差异相当大,不是吗?这是 dir() 返回的 14 个属性与 get_all_attributes() 返回的 76 个属性!让我们用 unique=False 分析后一个函数,这样我们就可以看到哪些来源提供了这 62 个额外的属性:
>>> get_all_X_dict = get_all_attributes(X, unique=False)
>>> {k: len(v) for k, v in get_all_X_dict.items()} # doctest: +NORMALIZE_WHITESPACE
{'dir': 14,
'__dict__': 15,
'inspect.getmembers': 16,
'metaclass': 62}
如您所见,dir() 返回的属性名称最少。这 14 个属性名称来自 dir() 并不在 X.__dict__ 中:
>>> [a for a in X.__dict__ if a not in dir_X] # doctest: +NORMALIZE_WHITESPACE
['_generate_next_value_', '_new_member_', '_use_args_',
'_member_names_', '_member_map_', '_value2member_map_',
'_unhashable_values_', '_member_type_', '_value_repr_',
'__new__']
>>> [a for a in dir_X if a not in X.__dict__] # doctest: +NORMALIZE_WHITESPACE
['__class__', '__contains__', '__getitem__',
'__init_subclass__', '__iter__', '__len__',
'__members__', '__name__', '__qualname__']
那么 inspect.getmembers() 呢?
>>> [a for a in dir_X if a not in get_all_X_dict["inspect.getmembers"]]
[]
>>> [a for a in get_all_X_dict["inspect.getmembers"] if a not in dir_X]
['name', 'value']
这次,inspect.getmembers() 返回了 dir() 的所有属性,但后者还包括两个额外的属性:"name" 和 "value"。
当然,包含最多属性的是元类源,其中大部分在其他任何源中都没有列出。
让我们看看这是如何应用于一个 X 值的,例如 X.X1。请注意,使用 get_all_attributes() 函数并设置 unique=False 就足够了,因为它也会显示 dir() 的输出:
>>> get_all_X1 = get_all_attributes(X.X1, False)
>>> {k: len(v) for k, v in get_all_X1.items()}
{'dir': 10, '__dict__': 4, 'inspect.getmembers': 10, 'metaclass': 14}
>>> from pprint import pp
>>> pp(get_all_X1)
{'dir': ['X1',
'X2',
'X3',
'__class__',
'__doc__',
'__eq__',
'__hash__',
'__module__',
'name',
'value'],
'__dict__': ['_value_', '_name_', '__objclass__', '_sort_order_'],
'inspect.getmembers': ['X1',
'X2',
'X3',
'__class__',
'__doc__',
'__eq__',
'__hash__',
'__module__',
'name',
'value'],
'metaclass': ['X1',
'X2',
'X3',
'__class__',
'__contains__',
'__doc__',
'__getitem__',
'__init_subclass__',
'__iter__',
'__len__',
'__members__',
'__module__',
'__name__',
'__qualname__']}
再次,我们看到了一些明显的差异。
结论
本文的目标不是分析 enum.Enum 类及其子类的复杂性,而是展示如何分析其属性。这就是为什么我们没有分析通过 get_all_attributes() 函数获得的结果。重点是 dir() 函数并不提供某些 Python 对象的所有属性,枚举类及其值只是这一现象的明显例子。
虽然 dir() 对于许多 Python 对象来说已经足够,但 get_all_attributes() 函数有时会提供一个更详细和全面的属性视图。这对于调试、开发库或任何需要深入了解对象属性的场合来说可能是至关重要的。
你可能认为你很少会用到这个函数,因为 dir() 通常会返回对象的所有属性。这是真的——但这里有一个问题:除非检查,否则你不会知道 dir() 的输出是否完整,而你可以使用 get_all_attributes() 函数来做这件事。所以,如果你的目标是仔细检查特定的对象,你应该使用这个函数而不是 dir()。使用 unique=False,你会看到更详细的视图,包括被 dir() 和其他来源捕获的属性。
脚注
¹ dir 这个名字很可能来源于 “directory”。这是因为它列出了对象的属性和方法,就像目录列出文件或包含的项目一样。因此,dir 这个名字反映了它对对象命名空间的目录视图。
² 文章中的大多数代码块都格式化为 docstests。你可以在以下文章中了解更多关于 doctesting 的信息:
2146

被折叠的 条评论
为什么被折叠?



