2.33. 自省

有少数几个内置函数能让正在运行的程序检视自身——它正在处理的值、这些值所在的命名空间,以及类之间的关系。当你需要根据一个对象实际是什么(而不是其调用者声称它是什么)来做决策时,就该动用它们。

2.33.1. 标识与哈希

  • id() —— 一个唯一的整数,在对象存活期间标识该对象。绑定同一对象的两个名称会返回相同的 id;两个相等但不同的对象则不会。

>>> a = [1, 2, 3]
>>> b = a
>>> c = [1, 2, 3]
>>> id(a) == id(b)        # same list
True
>>> id(a) == id(c)        # equal but distinct
False

id 在不同次运行之间不可移植,其含义也仅限于“同一对象还是不同对象”。

  • hash() —— 对象的哈希值,即 dictset 用来查找它的同一个数字。两个相等的对象哈希到相同的值;只有可哈希的类型(大多是不可变的值)才能使用。

>>> hash("abc")           # some integer, build-dependent
-1600925533
>>> hash([1, 2])
TypeError: unhashable type: 'list'

2.33.2. 查询类型和可调用性

  • type() —— 一个值的确切类。type(x) is int 问的是“x 是否恰好是 int”(不含子类);通常你真正想要的是 isinstance()

  • isinstance() —— “x 是这个类的实例,还是它某个子类的实例?” 这是在函数内部进行基于类型的分派的标准工具。

  • issubclass() —— 类级别的对应物。它接受两个类而不是一个实例。

  • callable() —— 如果参数可以用 () 调用则返回 True。当你收到一个可能是函数、也可能是普通值的参数时很有用。

>>> isinstance(3, int)
True
>>> isinstance(True, int)        # bool is a subclass of int
True
>>> issubclass(bool, int)
True
>>> callable(len)
True
>>> callable(10)
False

一个使用 callable 的模式:

def call_or_return(x):
    return x() if callable(x) else x

2.33.3. 查看作用域

  • globals() —— 以 dict 形式返回的模块全局命名空间。从中读取是可行的;向其中写入也是实实在在生效的,但在 REPL 探索之外这样做会让程序难以理解。

  • locals() —— 调用点处的局部命名空间。在函数内部它反映局部变量;但修改返回的字典并不保证会写回到实际的局部变量中(这是由实现定义的行为)。

name = "OpenMV"

def f():
    x = 10
    print(globals()["name"])    # OpenMV
    print(locals())             # {'x': 10}

这两个函数对调试以及需要发现已定义内容的工具很有用。在常规代码中请谨慎使用它们——一个会改动 globals() 的函数是 Python 中最难以推理的东西之一。

2.33.4. 另请参阅

dir()help()(在 调试 中介绍)是用于探索未知对象表面的日常自省工具。