Python 自省指南:https://www.ibm.com/developerworks/cn/linux/l-pyint/
From:https://my.oschina.net/taisha/blog/55597
什么是自省?
在日常生活中,自省(introspection)是一种自我检查行为。自省是指对某人自身思想、情绪、动机和行为的检查。伟大的哲学家苏格拉底将生命中的大部分时间用于自我检查,并鼓励他的雅典朋友们也这样做。他甚至对自己作出了这样的要求:“未经自省的生命不值得存在。”(请参阅 参考资料以获取关于苏格拉底更多信息的链接。)
在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。一旦您使用了支持自省的编程语言,就会产生类似这样的感觉:“未经检查的对象不值得实例化。”
本文介绍了 Python 编程语言的自省能力。整个 Python 语言对自省提供了深入而广泛的支持。实际上,很难想象假如 Python 语言没有其自省特性是什么样子。在读完本文时,您应该能够非常轻松地洞察到自己 Python 对象的“灵魂”。
在深入研究更高级的技术之前,我们尽可能用最普通的方式来研究 Python 自省。有些读者甚至可能会争论说:我们开始时所讨论的特性不应称之为“自省”。我们必须承认,它们是否属于自省的范畴还有待讨论。但从本文的主旨出发,我们所关心的只是找出有趣问题的答案。
现在让我们以交互方式使用 Python 来开始研究。当我们从命令行启动 Python 时,就进入了 Python shell,在这里可以输入 Python 代码,而且立刻会从 Python 解释器获得响应。(本文中列出的命令可以使用 Python 2.2.2 正确执行。如果您使用较早的版本,则可能产生不同的结果或发生错误。可以从 Python 网站下载最新版本 [请参阅 参考资料]。)
清单 1. 以交互方式启动 Python 解释器
1 2 3 4 5 |
|
在让 Python 运行起来,并看到 Python 提示符( >>>
)之后,您可能想知道 Python 能识别什么字。大多数编程语言都有保留字或关键字,这些字在该语言中有特殊的意义,Python 也不例外。您可能还注意到,Python 建议我们输入 help
以获取更多信息。也许我们可以向 Python 寻求一些关于关键字的帮助。
Python 的联机帮助实用程序
让我们按建议的那样,通过输入 help
来开始讨论,并观察它是否会向我们提供任何关于关键字的线索:
清单 2. 向 Python 寻求帮助
1 2 |
|
因为我们不知道哪些对象可能包含关键字,所以不指定任何特定对象来尝试 help()
:
清单 3. 启动帮助实用程序
1 2 3 4 5 6 7 8 9 10 11 12 |
|
现在,我们对此的理解似乎深入了些。让我们在 help 提示符下输入 keywords
:
清单 4. 用 keywords 寻求帮助
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
输入 help()
后,会看到一条欢迎消息和一些指示信息,接着是 help 提示符。在提示符下输入 keywords
,则会看到一个 Python 关键字列表。我们已经获得了问题的答案,于是退出帮助实用程序,这时会看到一条简短的告别消息,并返回到 Python 提示符下。
正如您从这个示例可以看到的,Python 的联机帮助实用程序会显示关于各种主题或特定对象的信息。帮助实用程序很有用,并确实利用了 Python 的自省能力。但仅仅使用帮助不会揭示帮助是如何获得其信息的。而且,因为本文的目的是揭示 Python 自省的所有秘密,所以我们必须迅速地跳出对帮助实用程序的讨论。
在结束关于帮助的讨论之前,让我们用它来获得一个可用模块的列表。模块只是包含 Python 代码的文本文件,其名称后缀是.py
。如果在 Python 提示符下输入 help('modules')
,或在 help 提示符下输入 modules
,则会看到一长列可用模块,类似于下面所示的部分列表。自己尝试它以观察您的系统中有哪些可用模块,并了解为什么会认为 Python 是“自带电池”的。
清单 5. 部分可用模块的列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
sys 模块
sys
模块是提供关于 Python 本身的详尽内在信息的模块。通过导入模块,并用点(.)符号引用其内容(如变量、函数和类)来使用模块。 sys
模块包含各种变量和函数,它们揭示了当前的 Python 解释器有趣的详细信息。让我们研究其中的一部分。我们要再次以交互方式运行 Python,并在 Python 命令提示符下输入命令。首先,我们将导入 sys
模块。然后,我们会输入sys.executable
变量,它包含到 Python 解释器的路径:
清单 6. 导入 sys 模块
1 2 3 4 5 6 7 |
|
当输入一行只包含对象名称的代码时,Python 通过显示该对象的表示进行响应,对于简单对象,往往显示对象的值。在本例中,因为所显示的值是用引号括起来的,所以我们得到一条线索: sys.executable
可能是字符串对象。稍后,我们将研究确定对象类型的其它更精确的方法,但只在 Python 提示符下输入对象名称是一种迅速而又方便的自省形式。
让我们研究 sys
模块其它一些有用的属性。
platform
变量告诉我们现在处于什么操作系统上:
sys.platform 属性
1 2 |
|
在当前的 Python 中,版本以字符串和元组(元组包含对象序列)来表示:
清单 8. sys.version 和 sys.version_info 属性
1 2 3 4 |
|
maxint
变量反映了可用的最大整数值:
sys.maxint 属性
1 2 |
|
argv
变量是一个包含命令行参数的列表(如果参数被指定的话)。第一项 argv[0] 是所运行脚本的路径。当我们以交互方式运行 Python 时,这个值是空字符串:
清单 10. sys.argv 属性
1 2 |
|
当运行其它 Python shell 时,如 PyCrust(有关 PyCrust 更多信息的链接,请参阅 参考资料),会看到类似于下面的信息:
清单 11. 使用 PyCrust 时的 sys.argv 属性
1 2 |
|
path
变量是模块搜索路径,Python 在导入期间将在其中的目录列表中寻找模块。最前面的空字符串 ''
是指当前目录:
清单 12. sys.path 属性
1 2 3 4 5 6 7 |
|
modules
变量是一个字典,它将当前已装入的所有模块的名称映射到模块对象。如您所见,缺省情况下,Python 装入一些特定的模块:
清单 13. sys.modules 属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
keyword 模块
让我们返回到关于 Python 关键字的问题。尽管帮助向我们显示了关键字列表,但事实证明一些帮助信息是硬编码的。关键字列表恰好是硬编码的,但毕竟它的自省程度不深。让我们研究一下,能否直接从 Python 标准库的某个模块中获取这个信息。如果在 Python 提示符下输入 help('modules keywords')
,则会看到如下信息:
清单 14. 同时使用 modules 和 keywords 寻求帮助
1 2 3 |
|
看起来, keyword
模块好象包含关键字。在文本编辑器中打开 keyword.py
文件,我们可以看到,Python 确实可以把关键字列表显式地用作 keyword
模块的 kwlist
属性。在 keyword
模块的注释中,我们还可以看到,该模块是根据 Python 本身的源代码自动生成的,这可以保证其关键字列表是准确而完整的:
清单 15. keyword 模块的关键字列表
1 2 3 4 5 |
|
dir() 函数
尽管查找和导入模块相对容易,但要记住每个模块包含什么却不是这么简单。您并不希望总是必须查看源代码来找出答案。幸运的是,Python 提供了一种方法,可以使用内置的 dir()
函数来检查模块(以及其它对象)的内容。
dir()
函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir()
返回当前作用域中的名称。让我们将 dir()
函数应用于 keyword
模块,并观察它揭示了什么:
清单 16. keyword 模块的属性
1 2 3 |
|
那么将它应用于我们先前讨论的 sys
模块会怎么样呢?
清单 17. sys 模块的属性
1 2 3 4 5 6 7 8 9 10 |
|
如果不带任何参数,则 dir()
返回当前作用域中的名称。请注意,因为我们先前导入了 keyword
和 sys
,所以它们出现在列表中。导入模块将把该模块的名称添加到当前作用域:
清单 18. 当前作用域中的名称
1 2 |
|
我们曾经提到 dir()
函数是内置函数,这意味着我们不必为了使用该函数而导入模块。不必做任何操作,Python 就可识别内置函数。现在,我们看到调用 dir()
后返回了这个名称 __builtins__
。也许此处有连接。让我们在 Python 提示符下输入名称 __builtins__
,并观察 Python 是否会告诉我们关于它的任何有趣的事情:
清单 19. __builtins__ 是什么?
1 2 |
|
因此 __builtins__
看起来象是当前作用域中绑定到名为 __builtin__
的模块对象的名称。(因为模块不是只有多个单一值的简单对象,所以 Python 改在尖括号中显示关于模块的信息。)注:如果您在磁盘上寻找 __builtin__.py
文件,将空手而归。这个特殊的模块对象是 Python 解释器凭空创建的,因为它包含着解释器始终可用的项。尽管看不到物理文件,但我们仍可以将 dir()
函数应用于这个对象,以观察所有内置函数、错误对象以及它所包含的几个杂项属性。
清单 20. __builtins__ 模块的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
dir()
函数适用于所有对象类型,包括字符串、整数、列表、元组、字典、函数、定制类、类实例和类方法。让我们将 dir()
应用于字符串对象,并观察 Python 返回什么。如您所见,即使简单的 Python 字符串也有许多属性:
清单 21. 字符串属性
1 2 3 4 5 6 7 8 9 10 |
|
自己尝试下列示例以观察它们返回什么。注: #
字符标记注释的开始。Python 将忽略从注释开始部分到该行结束之间的所有内容:
清单 22. 将 dir() 运用于其它对象
1 2 3 4 5 |
|
为了说明 Python 自省能力的动态本质,让我们研究将 dir()
运用于定制类和一些类实例的示例。我们将以交互方式定义自己的类,创建一些类的实例,仅向其中一个实例添加唯一的属性,并观察 Python 能否一直保存所有这些。以下是结果:
清单 23. 将 dir() 运用于定制类、类实例和属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
文档字符串
在许多 dir()
示例中,您可能会注意到的一个属性是 __doc__
属性。这个属性是一个字符串,它包含了描述对象的注释。Python 称之为文档字符串或 docstring,以下是其工作原理。如果模块、类、方法或函数定义的第一条语句是字符串,那么该字符串会作为对象的 __doc__
属性与该对象关联起来。例如,看一下 __builtins__
对象的文档字符串。因为文档字符串通常包含嵌入的换行 \n
,我们将使用 Python 的 print
语句,以便输出更易于阅读:
清单 24. 模块文档字符串
1 2 3 |
|
Python 甚至再次维持了在 Python shell 中以交互方式定义的类和方法上的文档字符串。让我们研究 Person
类及其 intro
方法的文档字符串:
清单 25. 类和方法文档字符串
1 2 3 4 |
|
因为文档字符串提供了如此有价值的信息,所以许多 Python 开发环境都有自动显示对象的文档字符串的方法。让我们再看一个 dir()
函数的文档字符串:
清单 26. 函数文档字符串
1 2 3 4 5 6 7 8 9 10 |
|
检查 Python 对象
我们好几次提到了“对象(object)”这个词,但一直没有真正定义它。编程环境中的对象很象现实世界中的对象。实际的对象有一定的形状、大小、重量和其它特征。实际的对象还能够对其环境进行响应、与其它对象交互或执行任务。计算机中的对象试图模拟我们身边现实世界中的对象,包括象文档、日程表和业务过程这样的抽象对象。
类似于实际的对象,几个计算机对象可能共享共同的特征,同时保持它们自己相对较小的变异特征。想一想您在书店中看到的书籍。书籍的每个物理副本都可能有污迹、几张破损的书页或唯一的标识号。尽管每本书都是唯一的对象,但都拥有相同标题的每本书都只是原始模板的实例,并保留了原始模板的大多数特征。
对于面向对象的类和类实例也是如此。例如,可以看到每个 Python 字符串都被赋予了一些属性, dir()
函数揭示了这些属性。在前一个示例中,我们定义了自己的 Person
类,它担任创建个别 Person 实例的模板,每个实例都有自己的 name 和 age 值,同时共享自我介绍的能力。这就是面向对象。
于是在计算机术语中,对象是拥有标识和值的事物,属于特定类型、具有特定特征和以特定方式执行操作。并且,对象从一个或多个父类继承了它们的许多属性。除了关键字和特殊符号(象运算符,如 +
、 -
、 *
、 **
、 /
、 %
、 <
、 >
等)外,Python 中的所有东西都是对象。Python 具有一组丰富的对象类型:字符串、整数、浮点、列表、元组、字典、函数、类、类实例、模块、文件等。
当您有一个任意的对象(也许是一个作为参数传递给函数的对象)时,可能希望知道一些关于该对象的情况。在本节中,我们将向您展示如何让 Python 对象回答如下问题:
- 对象的名称是什么?
- 这是哪种类型的对象?
- 对象知道些什么?
- 对象能做些什么?
- 对象的父对象是谁?
名称
并非所有对象都有名称,但那些有名称的对象都将名称存储在其 __name__
属性中。注:名称是从对象而不是引用该对象的变量中派生的。下面这个示例着重说明了这种区别:
清单 27. 名称中有什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
模块拥有名称,Python 解释器本身被认为是顶级模块或主模块。当以交互的方式运行 Python 时,局部 __name__
变量被赋予值 '__main__'
。同样地,当从命令行执行 Python 模块,而不是将其导入另一个模块时,其 __name__
属性被赋予值'__main__'
,而不是该模块的实际名称。这样,模块可以查看其自身的 __name__
值来自行确定它们自己正被如何使用,是作为另一个程序的支持,还是作为从命令行执行的主应用程序。因此,下面这条惯用的语句在 Python 模块中是很常见的:
清单 28. 用于执行或导入的测试
1 2 3 4 5 6 7 8 |
|
类型
type()
函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。它通过返回类型对象来做到这一点,可以将这个类型对象与 types
模块中定义的类型相比较:
清单 29. 我是您的类型吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
标识
先前我们说过,每个对象都有标识、类型和值。值得注意的是,可能有多个变量引用同一对象,同样地,变量可以引用看起来相似(有相同的类型和值),但拥有截然不同标识的多个对象。当更改对象时(如将某一项添加到列表),这种关于对象标识的概念尤其重要,如在下面的示例中, blist
和 clist
变量引用同一个列表对象。正如您在示例中所见, id()
函数给任何给定对象返回唯一的标识符:
清单 30. 目的地……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
属性
我们已经看到对象拥有属性,并且 dir()
函数会返回这些属性的列表。但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr()
和 getattr()
函数来完成,如本例所示:
清单 31. 具有一个属性;获得一个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
可调用
可以调用表示潜在行为(函数和方法)的对象。可以用 callable()
函数测试对象的可调用性:
清单 32. 您能为我做些事情吗?
1 2 3 4 5 6 7 8 |
|
实例
在 type()
函数提供对象的类型时,还可以使用 isinstance()
函数测试对象,以确定它是否是某个特定类型或定制类的实例:
清单 33. 您是那些实例中的一个吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
子类
我们先前提到过,定制类的实例从该类继承了属性。在类这一级别,可以根据一个类来定义另一个类,同样地,这个新类会按照层次化的方式继承属性。Python 甚至支持多重继承,多重继承意味着可以用多个父类来定义一个类,这个新类继承了多个父类。 issubclass()
函数使我们可以查看一个类是不是继承了另一个类:
清单 34. 您是我母亲吗?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
检查时间
让我们将上一节中讨论的几种检查技术结合起来。为了做到这一点,要定义自己的函数 — interrogate()
,它打印有关传递给它的任何对象的各种信息。以下是代码,后面是其用法的几个示例:
清单 35. 谁也没料到它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
|
接下来是Inspect模块
inspect模块主要提供了四种用处:
(1).对是否是模块,框架,函数等进行类型检查。
(2).获取源码
(3).获取类或函数的参数的信息
(4).解析堆栈
使用inspect模块可以提供自省功能,下面是关于自省的一些介绍:
首先通过一个例子来看一下本文中可能用到的对象和相关概念。
#coding: UTF-8
import sys # 模块,sys指向这个模块对象
import inspect
def foo(): pass # 函数,foo指向这个函数对象
class Cat(object): # 类,Cat指向这个类对象
def __init__ (self, name = ' kitty ' ):
self.name = name
def sayHi(self): # 实例方法,sayHi指向这个方法对象,使用类或实例.sayHi访问
print self.name, ' says Hi! ' # 访问名为name的字段,使用实例.name访问
cat = Cat() # cat是Cat类的实例对象
print Cat.sayHi # 使用类名访问实例方法时,方法是未绑定的(unbound)
print cat.sayHi # 使用实例访问实例方法时,方法是绑定的(bound)
有时候我们会碰到这样的需求,需要执行对象的某个方法,或是需要对对象的某个字段赋值,而方法名或是字段名在编码代码时并不能确定,需要通过参数传递字符串的形式输入。举个具体的例子:当我们需要实现一个通用的DBM框架时,可能需要对数据对象的字段赋值,但我们无法预知用到这个框架的数据对象都有些什么字段,换言之,我们在写框架的时候需要通过某种机制访问未知的属性。
这个机制被称为反射(反过来让对象告诉我们他是什么),或是自省(让对象自己告诉我们他是什么,好吧我承认括号里是我瞎掰的- -#),用于实现在运行时获取未知对象的信息。反射是个很吓唬人的名词,听起来高深莫测,在一般的编程语言里反射相对其他概念来说稍显复杂,一般来说都是作为高级主题来讲;但在Python中反射非常简单,用起来几乎感觉不到与其他的代码有区别,使用反射获取到的函数和方法可以像平常一样加上括号直接调用,获取到类后可以直接构造实例;不过获取到的字段不能直接赋值,因为拿到的其实是另一个指向同一个地方的引用,赋值只能改变当前的这个引用而已。
1. 访问对象的属性
以下列出了几个内建方法,可以用来检查或是访问对象的属性。这些方法可以用于任意对象而不仅仅是例子中的Cat实例对象;Python中一切都是对象。
cat = Cat('kitty')
print cat.name # 访问实例属性 cat.sayHi() # 调用实例方法
print dir(cat) # 获取实例的属性名,以列表形式返回 if hasattr(cat, ' name ' ): # 检查实例是否有这个属性 setattr(cat, ' name' , ' tiger ' ) # same as: a.name = 'tiger' print getattr(cat, ' name ' ) # same as: print a.name getattr(cat, 'sayHi ' )() # same as: cat.sayHi()
- dir([obj]):
调用这个方法将返回包含obj大多数属性名的列表(会有一些特殊的属性不包含在内)。obj的默认值是当前的模块对象。 - hasattr(obj, attr):
这个方法用于检查obj是否有一个名为attr的值的属性,返回一个布尔值。 - getattr(obj, attr):
调用这个方法将返回obj中名为attr值的属性的值,例如如果attr为'bar',则返回obj.bar。 - setattr(obj, attr, val):
调用这个方法将给obj的名为attr的值的属性赋值为val。例如如果attr为'bar',则相当于obj.bar = val。
2. 访问对象的元数据
当你对一个你构造的对象使用dir()时,可能会发现列表中的很多属性并不是你定义的。这些属性一般保存了对象的元数据,比如类的__name__属性保存了类名。大部分这些属性都可以修改,不过改动它们意义并不是很大;修改其中某些属性如function.func_code还可能导致很难发现的问题,所以改改name什么的就好了,其他的属性不要在不了解后果的情况下修改。
接下来列出特定对象的一些特殊属性。另外,Python的文档中有提到部分属性不一定会一直提供,下文中将以红色的星号*标记,使用前你可以先打开解释器确认一下。
2.0. 准备工作:确定对象的类型
在types模块中定义了全部的Python内置类型,结合内置方法isinstance()就可以确定对象的具体类型了。
- isinstance(object, classinfo):
检查object是不是classinfo中列举出的类型,返回布尔值。classinfo可以是一个具体的类型,也可以是多个类型的元组或列表。
types模块中仅仅定义了类型,而inspect模块中封装了很多检查类型的方法,比直接使用types模块更为轻松,所以这里不给出关于types的更多介绍,如有需要可以直接查看types模块的文档说明。本文第3节中介绍了inspect模块。
2.1. 模块(module)
- __doc__: 文档字符串。如果模块没有文档,这个值是None。
- *__name__: 始终是定义时的模块名;即使你使用import .. as 为它取了别名,或是赋值给了另一个变量名。
- *__dict__: 包含了模块里可用的属性名-属性的字典;也就是可以使用模块名.属性名访问的对象。
- __file__: 包含了该模块的文件路径。需要注意的是内建的模块没有这个属性,访问它会抛出异常!
import fnmatch as m
print m. __doc__ .splitlines()[0] # Filename matching with shell patterns.
print m. __name__ # fnmatch
print m. __file__ # /usr/lib/python2.6/fnmatch.pyc
print m. __dict__ .items()[0] # ('fnmatchcase', )
2.2. 类(class)
- __doc__: 文档字符串。如果类没有文档,这个值是None。
- *__name__: 始终是定义时的类名。
- *__dict__: 包含了类里可用的属性名-属性的字典;也就是可以使用类名.属性名访问的对象。
- __module__: 包含该类的定义的模块名;需要注意,是字符串形式的模块名而不是模块对象。
- *__bases__: 直接父类对象的元组;但不包含继承树更上层的其他类,比如父类的父类。
print Cat.__doc__ # None
print Cat. __name__ # Cat
print Cat. __module__ # __main__
print Cat. __bases__ # (,)
print Cat. __dict__ # {'__module__': '__main__', ...}
2.3. 实例(instance)
实例是指类实例化以后的对象。
- *__dict__: 包含了可用的属性名-属性字典。
- *__class__: 该实例的类对象。对于类Cat,cat.__class__ == Cat 为 True。
print cat.__dict__
print cat. __class__
print cat. __class__ == Cat # True
2.4. 内建函数和方法(built-in functions and methods)
根据定义,内建的(built-in)模块是指使用C写的模块,可以通过sys模块的builtin_module_names字段查看都有哪些模块是内建的。这些模块中的函数和方法可以使用的属性比较少,不过一般也不需要在代码中查看它们的信息。
- __doc__: 函数或方法的文档。
- __name__: 函数或方法定义时的名字。
- __self__: 仅方法可用,如果是绑定的(bound),则指向调用该方法的类(如果是类方法)或实例(如果是实例方法),否则为None。
- *__module__: 函数或方法所在的模块名。
2.5. 函数(function)
这里特指非内建的函数。注意,在类中使用def定义的是方法,方法与函数虽然有相似的行为,但它们是不同的概念。
- __doc__: 函数的文档;另外也可以用属性名func_doc。
- __name__: 函数定义时的函数名;另外也可以用属性名func_name。
- *__module__: 包含该函数定义的模块名;同样注意,是模块名而不是模块对象。
- *__dict__: 函数的可用属性;另外也可以用属性名func_dict。
不要忘了函数也是对象,可以使用函数.属性名访问属性(赋值时如果属性不存在将新增一个),或使用内置函数has/get/setattr()访问。不过,在函数中保存属性的意义并不大。 - func_defaults: 这个属性保存了函数的参数默认值元组;因为默认值总是靠后的参数才有,所以不使用字典的形式也是可以与参数对应上的。
- func_code: 这个属性指向一个该函数对应的code对象,code对象中定义了其他的一些特殊属性,将在下文中另外介绍。
- func_globals: 这个属性指向当前的全局命名空间而不是定义函数时的全局命名空间,用处不大,并且是只读的。
- *func_closure: 这个属性仅当函数是一个闭包时有效,指向一个保存了所引用到的外部函数的变量cell的元组,如果该函数不是一个内部函数,则始终为None。这个属性也是只读的。
2.6. 方法(method)
方法虽然不是函数,但可以理解为在函数外面加了一层外壳;拿到方法里实际的函数以后,就可以使用2.5节的属性了。
- __doc__: 与函数相同。
- __name__: 与函数相同。
- *__module__: 与函数相同。
- im_func: 使用这个属性可以拿到方法里实际的函数对象的引用。另外如果是2.6以上的版本,还可以使用属性名__func__。
- im_self: 如果是绑定的(bound),则指向调用该方法的类(如果是类方法)或实例(如果是实例方法),否则为None。如果是2.6以上的版本,还可以使用属性名__self__。
- im_class: 实际调用该方法的类,或实际调用该方法的实例的类。注意不是方法的定义所在的类,如果有继承关系的话。
im = cat.sayHi
print im.im_func
print im.im_self # cat
print im.im_class # Cat
这里讨论的是一般的实例方法,另外还有两种特殊的方法分别是类方法(classmethod)和静态方法(staticmethod)。类方法还是方法,不过因为需要使用类名调用,所以他始终是绑定的;而静态方法可以看成是在类的命名空间里的函数(需要使用类名调用的函数),它只能使用函数的属性,不能使用方法的属性。
2.7. 生成器(generator)
生成器是调用一个生成器函数(generator function)返回的对象,多用于集合对象的迭代。
- __iter__: 仅仅是一个可迭代的标记。
- gi_code: 生成器对应的code对象。
- gi_frame: 生成器对应的frame对象。
- gi_running: 生成器函数是否在执行。生成器函数在yield以后、执行yield的下一行代码前处于frozen状态,此时这个属性的值为0。
- next|close|send|throw: 这是几个可调用的方法,并不包含元数据信息,如何使用可以查看生成器的相关文档。
def gen():
for n in xrange( 5 ):
yield n
g = gen()
print g # <generator object gen at 0x...>
print g.gi_code # <code object gen at 0x...>
print g.gi_frame # <frame object at 0x...>
print g.gi_running # 0
print g.next() # 0
print g.next() # 1
for n in g:
print n, # 2 3 4
接下来讨论的是几个不常用到的内置对象类型。这些类型在正常的编码过程中应该很少接触,除非你正在自己实现一个解释器或开发环境之类。所以这里只列出一部分属性,如果需要一份完整的属性表或想进一步了解,可以查看文末列出的参考文档。
2.8. 代码块(code)
代码块可以由类源代码、函数源代码或是一个简单的语句代码编译得到。这里我们只考虑它指代一个函数时的情况;2.5节中我们曾提到可以使用函数的func_code属性获取到它。code的属性全部是只读的。
- co_argcount: 普通参数的总数,不包括*参数和**参数。
- co_names: 所有的参数名(包括*参数和**参数)和局部变量名的元组。
- co_varnames: 所有的局部变量名的元组。
- co_filename: 源代码所在的文件名。
- co_flags: 这是一个数值,每一个二进制位都包含了特定信息。较关注的是0b100(0x4)和0b1000(0x8),如果co_flags & 0b100 != 0,说明使用了*args参数;如果co_flags & 0b1000 != 0,说明使用了**kwargs参数。另外,如果co_flags & 0b100000(0x20) != 0,则说明这是一个生成器函数(generator function)。
co = cat.sayHi.func_code
print co.co_argcount # 1
print co.co_names # ('name',)
print co.co_varnames # ('self',)
print co.co_flags & 0b100 # 0
2.9. 栈帧(frame)
栈帧表示程序运行时函数调用栈中的某一帧。函数没有属性可以获取它,因为它在函数调用时才会产生,而生成器则是由函数调用返回的,所以有属性指向栈帧。想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取。你可以使用sys模块的_getframe()函数、或inspect模块的currentframe()函数获取当前栈帧。这里列出来的属性全部是只读的。
- f_back: 调用栈的前一帧。
- f_code: 栈帧对应的code对象。
- f_locals: 用在当前栈帧时与内建函数locals()相同,但你可以先获取其他帧然后使用这个属性获取那个帧的locals()。
- f_globals: 用在当前栈帧时与内建函数globals()相同,但你可以先获取其他帧……。
def add(x, y=1):
f = inspect.currentframe()
print f.f_locals # same as locals()
print f.f_back # <frame object at 0x...>
return x + y
add( 2 )
2.10. 追踪(traceback)
追踪是在出现异常时用于回溯的对象,与栈帧相反。由于异常时才会构建,而异常未捕获时会一直向外层栈帧抛出,所以需要使用try才能见到这个对象。你可以使用sys模块的exc_info()函数获得它,这个函数返回一个元组,元素分别是异常类型、异常对象、追踪。traceback的属性全部是只读的。
- tb_next: 追踪的下一个追踪对象。
- tb_frame: 当前追踪对应的栈帧。
- tb_lineno: 当前追踪的行号。
def div(x, y):
try :
return x / y
except :
tb = sys.exc_info()[ 2 ] # return (exc_type, exc_value, traceback)
print tb
print tb.tb_lineno # "return x/y" 的行号
div( 1 , 0)
3. 使用inspect模块
inspect模块提供了一系列函数用于帮助使用自省。下面仅列出较常用的一些函数,想获得全部的函数资料可以查看inspect模块的文档。
3.1. 检查对象类型
- is{module|class|function|method|builtin}(obj):
检查对象是否为模块、类、函数、方法、内建函数或方法。 - isroutine(obj):
用于检查对象是否为函数、方法、内建函数或方法等等可调用类型。用这个方法会比多个is*()更方便,不过它的实现仍然是用了多个is*()。
对于实现了__call__的类实例,这个方法会返回False。如果目的是只要可以直接调用就需要是True的话,不妨使用isinstance(obj, collections.Callable)这种形式。我也不知道为什么Callable会在collections模块中,抱歉!我大概是因为collections模块中包含了很多其他的ABC(Abstract Base Class)的缘故吧:)im = cat.sayHiif inspect.isroutine(im): im()
3.2. 获取对象信息
- getmembers(object[, predicate]):
这个方法是dir()的扩展版,它会将dir()找到的名字对应的属性一并返回,形如[(name, value), ...]。另外,predicate是一个方法的引用,如果指定,则应当接受value作为参数并返回一个布尔值,如果为False,相应的属性将不会返回。使用is*作为第二个参数可以过滤出指定类型的属性。 - getmodule(object):
还在为第2节中的__module__属性只返回字符串而遗憾吗?这个方法一定可以满足你,它返回object的定义所在的模块对象。 - get{file|sourcefile}(object):
获取object的定义所在的模块的文件名|源代码文件名(如果没有则返回None)。用于内建的对象(内建模块、类、函数、方法)上时会抛出TypeError异常。 - get{source|sourcelines}(object):
获取object的定义的源代码,以字符串|字符串列表返回。代码无法访问时会抛出IOError异常。只能用于module/class/function/method/code/frame/traceack对象。 - getargspec(func):
仅用于方法,获取方法声明的参数,返回元组,分别是(普通参数名的列表, *参数名, **参数名, 默认值元组)。如果没有值,将是空列表和3个None。如果是2.6以上版本,将返回一个命名元组(Named Tuple),即除了索引外还可以使用属性名访问元组中的元素。 - getargvalues(frame):
仅用于栈帧,获取栈帧中保存的该次函数调用的参数值,返回元组,分别是(普通参数名的列表, *参数名, **参数名, 帧的locals())。如果是2.6以上版本,将返回一个命名元组(Named Tuple),即除了索引外还可以使用属性名访问元组中的元素。 - getcallargs(func[, *args][, **kwds]):
返回使用args和kwds调用该方法时各参数对应的值的字典。这个方法仅在2.7版本中才有。 - getmro(cls):
返回一个类型元组,查找类属性时按照这个元组中的顺序。如果是新式类,与cls.__mro__结果一样。但旧式类没有__mro__这个属性,直接使用这个属性会报异常,所以这个方法还是有它的价值的。 返回当前的栈帧对象。
其他的操作frame和traceback的函数请查阅inspect模块的文档,用的比较少,这里就不多介绍了。