别让 Python 的 dir() 函数骗了你!

原文:towardsdatascience.com/dont-let-the-python-dir-function-trick-you-3c42abbdb4d9

PYTHON 编程

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/39da923d7ce923468dc60267471f133c.png

这真的是全部吗?dir() 函数并不能保证显示所有内容!图片由 Cristiano PintoUnsplash 提供

这就是 Python 的内置 dir() 函数:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4b89e7c46fad9149e5644a33104ce490.png

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() 函数,该函数结合了三种内省技术,以提供一个对象属性的全面概述:

  1. 内置的 dir() 函数。

  2. 对象的 __dict__ 属性,如果有的话。__dict__ 通常包含对象的所有可写属性。

  3. Python 标准库中的 inspect 模块。我们将使用 inspect.getmembers() 函数,因为它提供了对象的全部成员的完整列表,不仅限于 __dict__ 中的成员,因此它包括动态添加的属性、继承的属性等等。

  4. 元类信息。我们将从对象的元类(即类的类)中提取属性,这可以提供对类级别方法和属性的见解。

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,一个布尔标志。当 uniqueTrue 时,该函数返回一个包含 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 的信息:

使用 doctest 进行 Python 文档测试:简单易行

### Python 中 `dir()` 函数的使用说明 `dir()` 是 Python 的内置函数之一,主要用于返回指定对象的所有属性和方法名称列表。如果未传入任何参数,则会返回当前本地作用域内的变量名列表[^1]。 #### 基本用法 当调用 `dir(object)` 时,它会返回该对象的所有有效属性和方法名称列表。对于模块、类实例或其他复杂数据类型来说,这有助于快速了解其支持的功能集合[^2]。 以下是几个简单的例子展示如何使用 `dir()`: ```python # 示例 1: 查看字符串类型的可用方法 print(dir(str)) # 示例 2: 查看整数类型的可用方法 print(dir(int)) # 示例 3: 当前命名空间中的全局变量 import math print(dir()) # 显示包括 'math' 在内的所有定义的名字 ``` #### 特殊用法 除了常规的对象外,还可以通过传递特定的数据结构或者自定义类给 `dir()` 来获取更详细的内部实现细节。例如,在处理文件操作时,可以通过如下方式查看文件对象的方法集[^3]: ```python with open('example.txt', 'r') as file: print(dir(file)) # 列出文件对象上的可访问成员 ``` 另外需要注意的是,某些隐藏字段(即以双下划线开头并结尾的特殊方法)也会被包含进来,这些通常是由解释器使用的私有接口^。 #### 返回值与输出 无论输入是什么样的目标实体,只要能够成功解析到对应的信息体,那么最终都会得到一个由字符串构成的列表形式的结果作为回应;其中每一个条目代表了一个合法的操作符或者是特性描述词^。 #### 注意事项与高级技巧 虽然此工具非常强大且方便快捷地提供了关于某个具体事物的知识概览图谱,但在实际开发过程中也需谨慎对待所获得的内容因为并非所有的列出项都是公开API的一部分可能涉及到版本兼容性等问题因此建议仅将其作为一种辅助手段而非唯一依据来进行编码决策制定过程. --- ### 小结 综上所述,掌握了上述要点之后就可以灵活运用 python 自带的 dir 功能去探究未知领域啦! 不过记得合理利用哦~
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值