什么是自省?
在日常生活中,自省(introspection)是一种自我检查行为。自省是指对某人自身思想、情绪、动机和行为的检查。伟大的哲学家苏格拉底将生命中的大部分时间用于自我检查,并鼓励他的雅典朋友们也这样做。他甚至对自己作出了这样的要求:“未经自省的生命不值得存在。”
在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。一旦您使用了支持自省的编程语言,就会产生类似这样的感觉:“未经检查的对象不值得实例化。”
本文介绍了 Python 编程语言的自省能力。整个 Python 语言对自省提供了深入而广泛的支持。实际上,很难想象假如 Python 语言没有其自省特性是什么样子。在读完本文时,您应该能够非常轻松地洞察到自己 Python 对象的“灵魂”。
在深入研究更高级的技术之前,我们尽可能用最普通的方式来研究 Python 自省。有些读者甚至可能会争论说:我们开始时所讨论的特性不应称之为“自省”。我们必须承认,它们是否属于自省的范畴还有待讨论。但从本文的主旨出发,我们所关心的只是找出有趣问题的答案。
现在让我们以交互方式使用 Python 来开始研究。当我们从命令行启动 Python 时,就进入了 Python shell,在这里可以输入 Python 代码,而且立刻会从 Python 解释器获得响应。(本文中列出的命令可以使用 Python 2.2.2 正确执行。如果您使用较早的版本,则可能产生不同的结果或发生错误。可以从 Python 网站下载最新版本)
清单 1. 以交互方式启动 Python 解释器 |
在让 Python 运行起来,并看到 Python 提示符(>>>)之后,您可能想知道 Python 能识别什么字。大多数编程语言都有保留字或关键字,这些字在该语言中有特殊的意义,Python 也不例外。您可能还注意到,Python 建议我们输入 help 以获取更多信息。也许我们可以向 Python 寻求一些关于关键字的帮助。
Python 的联机帮助实用程序
让我们按建议的那样,通过输入 help 来开始讨论,并观察它是否会向我们提供任何关于关键字的线索:
|
因为我们不知道哪些对象可能包含关键字,所以不指定任何特定对象来尝试 help():
|
现在,我们对此的理解似乎深入了些。让我们在 help 提示符下输入 keywords:
|
输入 help() 后,会看到一条欢迎消息和一些指示信息,接着是 help 提示符。在提示符下输入 keywords,则会看到一个 Python 关键字列表。我们已经获得了问题的答案,于是退出帮助实用程序,这时会看到一条简短的告别消息,并返回到 Python 提示符下。
正如您从这个示例可以看到的,Python 的联机帮助实用程序会显示关于各种主题或特定对象的信息。帮助实用程序很有用,并确实利用了 Python 的自省能力。但仅仅使用帮助不会揭示帮助是如何获得其信息的。而且,因为本文的目的是揭示 Python 自省的所有秘密,所以我们必须迅速地跳出对帮助实用程序的讨论。
在结束关于帮助的讨论之前,让我们用它来获得一个可用模块的列表。模块只是包含 Python 代码的文本文件,其名称后缀是 .py。如果在 Python 提示符下输入 help('modules'),或在 help 提示符下输入 modules,则会看到一长列可用模块,类似于下面所示的部分列表。自己尝试它以观察您的系统中有哪些可用模块,并了解为什么会认为 Python 是“自带电池”的。
|
sys 模块sys 模块是提供关于 Python 本身的详尽内在信息的模块。通过导入模块,并用点(.)符号引用其内容(如变量、函数和类)来使用模块。sys 模块包含各种变量和函数,它们揭示了当前的 Python 解释器有趣的详细信息。让我们研究其中的一部分。我们要再次以交互方式运行 Python,并在 Python 命令提示符下输入命令。首先,我们将导入 sys 模块。然后,我们会输入 sys.executable 变量,它包含到 Python 解释器的路径:
|
当输入一行只包含对象名称的代码时,Python 通过显示该对象的表示进行响应,对于简单对象,往往显示对象的值。在本例中,因为所显示的值是用引号括起来的,所以我们得到一条线索:sys.executable 可能是字符串对象。稍后,我们将研究确定对象类型的其它更精确的方法,但只在 Python 提示符下输入对象名称是一种迅速而又方便的自省形式。
让我们研究 sys 模块其它一些有用的属性。
platform 变量告诉我们现在处于什么操作系统上:
|
在当前的 Python 中,版本以字符串和元组(元组包含对象序列)来表示:
清单 8. sys.version 和 sys.version_info 属性 |
maxint 变量反映了可用的最大整数值:
|
argv 变量是一个包含命令行参数的列表(如果参数被指定的话)。第一项 argv[0] 是所运行脚本的路径。当我们以交互方式运行 Python 时,这个值是空字符串:
|
当运行其它 Python shell 时,如 PyCrust,会看到类似于下面的信息:
清单 11. 使用 PyCrust 时的 sys.argv 属性 |
path 变量是模块搜索路径,Python 在导入期间将在其中的目录列表中寻找模块。最前面的空字符串 '' 是指当前目录:
|
modules 变量是一个字典,它将当前已装入的所有模块的名称映射到模块对象。如您所见,缺省情况下,Python 装入一些特定的模块:
|
keyword 模块
让我们返回到关于 Python 关键字的问题。尽管帮助向我们显示了关键字列表,但事实证明一些帮助信息是硬编码的。关键字列表恰好是硬编码的,但毕竟它的自省程度不深。让我们研究一下,能否直接从 Python 标准库的某个模块中获取这个信息。如果在 Python 提示符下输入 help('modules keywords'),则会看到如下信息:
|
看起来,keyword 模块好象包含关键字。在文本编辑器中打开 keyword.py 文件,我们可以看到,Python 确实可以把关键字列表显式地用作 keyword 模块的 kwlist 属性。在 keyword 模块的注释中,我们还可以看到,该模块是根据 Python 本身的源代码自动生成的,这可以保证其关键字列表是准确而完整的:
|
dir() 函数
尽管查找和导入模块相对容易,但要记住每个模块包含什么却不是这么简单。您并不希望总是必须查看源代码来找出答案。幸运的是,Python 提供了一种方法,可以使用内置的 dir() 函数来检查模块(以及其它对象)的内容。
dir() 函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中的名称。让我们将 dir() 函数应用于 keyword 模块,并观察它揭示了什么:
|
那么将它应用于我们先前讨论的 sys 模块会怎么样呢?
|
如果不带任何参数,则 dir() 返回当前作用域中的名称。请注意,因为我们先前导入了 keyword 和 sys,所以它们出现在列表中。导入模块将把该模块的名称添加到当前作用域:
|
我们曾经提到 dir() 函数是内置函数,这意味着我们不必为了使用该函数而导入模块。不必做任何操作,Python 就可识别内置函数。现在,我们看到调用 dir() 后返回了这个名称 __builtins__。也许此处有连接。让我们在 Python 提示符下输入名称 __builtins__,并观察 Python 是否会告诉我们关于它的任何有趣的事情:
|
因此 __builtins__ 看起来象是当前作用域中绑定到名为 __builtin__ 的模块对象的名称。(因为模块不是只有多个单一值的简单对象,所以 Python 改在尖括号中显示关于模块的信息。)注:如果您在磁盘上寻找 __builtin__.py 文件,将空手而归。这个特殊的模块对象是 Python 解释器凭空创建的,因为它包含着解释器始终可用的项。尽管看不到物理文件,但我们仍可以将 dir() 函数应用于这个对象,以观察所有内置函数、错误对象以及它所包含的几个杂项属性。
|
dir() 函数适用于所有对象类型,包括字符串、整数、列表、元组、字典、函数、定制类、类实例和类方法。让我们将 dir() 应用于字符串对象,并观察 Python 返回什么。如您所见,即使简单的 Python 字符串也有许多属性:
|
自己尝试下列示例以观察它们返回什么。注:# 字符标记注释的开始。Python 将忽略从注释开始部分到该行结束之间的所有内容:
|
为了说明 Python 自省能力的动态本质,让我们研究将 dir() 运用于定制类和一些类实例的示例。我们将以交互方式定义自己的类,创建一些类的实例,仅向其中一个实例添加唯一的属性,并观察 Python 能否一直保存所有这些。以下是结果:
|
文档字符串
在许多 dir() 示例中,您可能会注意到的一个属性是 __doc__ 属性。这个属性是一个字符串,它包含了描述对象的注释。Python 称之为文档字符串或 docstring,以下是其工作原理。如果模块、类、方法或函数定义的第一条语句是字符串,那么该字符串会作为对象的 __doc__ 属性与该对象关联起来。例如,看一下 __builtins__ 对象的文档字符串。因为文档字符串通常包含嵌入的换行 \n,我们将使用 Python 的 print 语句,以便输出更易于阅读:
|
Python 甚至再次维持了在 Python shell 中以交互方式定义的类和方法上的文档字符串。让我们研究 Person 类及其 intro 方法的文档字符串:
|
因为文档字符串提供了如此有价值的信息,所以许多 Python 开发环境都有自动显示对象的文档字符串的方法。让我们再看一个 dir() 函数的文档字符串:
|