在日常生活中,自省(introspection)是一种自我检查行为。自省是指对某人自身思想、情绪、动机和行为的检查。伟大的哲学家苏格拉底将生命中的大部分时间用于自我检查,并鼓励他的雅典朋友们也这样做。他甚至对自己作出了这样的要求:“未经自省的生命不值得存在。”
在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。一旦您使用了支持自省的编程语言,就会产生类似这样的感觉:“未经检查的对象不值得实例化。”
本文介绍了 Python 编程语言的自省能力。整个 Python 语言对自省提供了深入而广泛的支持。实际上,很难想象假如 Python 语言没有其自省特性是什么样子。在读完本文时,您应该能够非常轻松地洞察到自己 Python 对象的“灵魂”。
在深入研究更高级的技术之前,我们尽可能用最普通的方式来研究 Python 自省。有些读者甚至可能会争论说:我们开始时所讨论的特性不应称之为“自省”。我们必须承认,它们是否属于自省的范畴还有待讨论。但从本文的主旨出发,我们所关心的只是找出有趣问题的答案。
现在让我们以交互方式使用 Python 来开始研究。当我们从命令行启动 Python 时,就进入了 Python shell,在这里可以输入 Python 代码,而且立刻会从 Python 解释器获得响应。(本文中列出的命令可以使用 Python 2.2.2 正确执行。如果您使用较早的版本,则可能产生不同的结果或发生错误。可以从 Python 网站下载最新版本。)
- $ python
- Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
- [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
- Type "help", "copyright", "credits" or "license" for more information.
- >>>
在让 Python 运行起来,并看到 Python 提示符(>>> )之后,您可能想知道 Python 能识别什么字。大多数编程语言都有保留字或关键字,这些字在该语言中有特殊的意义,Python 也不例外。您可能还注意到,Python 建议我们输入help 以获取更多信息。也许我们可以向 Python 寻求一些关于关键字的帮助。
让我们按建议的那样,通过输入
help 来开始讨论,并观察它是否会向我们提供任何关于关键字的线索:
- >>> help
- Type help() for interactive help, or help(object) for help about object.
因为我们不知道哪些对象可能包含关键字,所以不指定任何特定对象来尝试help() :
- >>> help()
- Welcome to Python 2.2! This is the online help utility.
- If this is your first time using Python, you should definitely check out
- the tutorial on the Internet at http://www.python.org/doc/tut/.
- Enter the name of any module, keyword, or topic to get help on writing
- Python programs and using Python modules. To quit this help utility and
- return to the interpreter, just type "quit".
- To get a list of available modules, keywords, or topics, type "modules",
- "keywords", or "topics". Each module also comes with a one-line summary
- of what it does; to list the modules whose summaries contain a given word
- such as "spam", type "modules spam".
- help>
现在,我们对此的理解似乎深入了些。让我们在 help 提示符下输入keywords :
- help> keywords
- Here is a list of the Python keywords. Enter any keyword to get more help.
- and elif global or
- assert else if pass
- break except import print
- class exec in raise
- continue finally is return
- def for lambda try
- del from not while
- help> quit
- You are now leaving help and returning to the Python interpreter.
- If you want to ask for help on a particular object directly from the
- interpreter, you can type "help(object)". Executing "help('string')"
- has the same effect as typing a particular string at the help> prompt.
- >>>
输入help() 后,会看到一条欢迎消息和一些指示信息,接着是 help 提示符。在提示符下输入
keywords ,则会看到一个 Python 关键字列表。我们已经获得了问题的答案,于是退出帮助实用程序,这时会看到一条简短的告别消息,并返回到 Python 提示符下。
正如您从这个示例可以看到的,Python 的联机帮助实用程序会显示关于各种主题或特定对象的信息。帮助实用程序很有用,并确实利用了 Python 的自省能力。但仅仅使用帮助不会揭示帮助是如何获得其信息的。而且,因为本文的目的是揭示 Python 自省的所有秘密,所以我们必须迅速地跳出对帮助实用程序的讨论。
在结束关于帮助的讨论之前,让我们用它来获得一个可用模块的列表。模块只是包含 Python 代码的文本文件,其名称后缀是.py 。如果在 Python 提示符下输入help('modules') ,或在 help 提示符下输入modules ,则会看到一长列可用模块,类似于下面所示的部分列表。自己尝试它以观察您的系统中有哪些可用模块,并了解为什么会认为 Python 是“自带电池”的。
- >>> help('modules')
- Please wait a moment while I gather a list of all available modules...
- BaseHTTPServer cgitb marshal sndhdr
- Bastion chunk math socket
- CDROM cmath md5 sre
- CGIHTTPServer cmd mhlib sre_compile
- Canvas code mimetools sre_constants
- <...>
- bisect macpath signal xreadlines
- cPickle macurl2path site xxsubtype
- cStringIO mailbox slgc (package) zipfile
- calendar mailcap smtpd
- cgi markupbase smtplib
- Enter any module name to get more help. Or, type "modules spam" to search
- for modules whose descriptions contain the word "spam".
- >>>
sys 模块是提供关于 Python 本身的详尽内在信息的模块。通过导入模块,并用点(.)符号引用其内容(如变量、函数和类)来使用模块。
sys 模块包含各种变量和函数,它们揭示了当前的 Python 解释器有趣的详细信息。让我们研究其中的一部分。我们要再次以交互方式运行 Python,并在 Python 命令提示符下输入命令。首先,我们将导入sys 模块。然后,我们会输入sys.executable 变量,它包含到 Python 解释器的路径:
- $ python
- Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
- [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
- Type "help", "copyright", "credits" or "license" for more information.
- >>> import sys
- >>> sys.executable
- '/usr/local/bin/python'
当输入一行只包含对象名称的代码时,Python 通过显示该对象的表示进行响应,对于简单对象,往往显示对象的值。在本例中,因为所显示的值是用引号括起来的,所以我们得到一条线索:
sys.executable 可能是字符串对象。稍后,我们将研究确定对象类型的其它更精确的方法,但只在 Python 提示符下输入对象名称是一种迅速而又方便的自省形式。
让我们研究
sys 模块其它一些有用的属性。
platform 变量告诉我们现在处于什么操作系统上:
- >>> sys.platform
- 'linux2'
在当前的 Python 中,版本以字符串和元组(元组包含对象序列)来表示:
- >>> sys.version
- '2.2.2 (#1, Oct 28 2002, 17:22:19) \n[GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)]'
- >>> sys.version_info
- (2, 2, 2, 'final', 0)
maxint 变量反映了可用的最大整数值:
- >>> sys.maxint
- 2147483647
argv 变量是一个包含命令行参数的列表(如果参数被指定的话)。第一项 argv[0] 是所运行脚本的路径。当我们以交互方式运行 Python 时,这个值是空字符串:
- >>> sys.argv
- ['']
当运行其它 Python shell 时,如 PyCrust,会看到类似于下面的信息:
- >>> sys.argv[0]
- '/home/pobrien/Code/PyCrust/PyCrustApp.py'
path 变量是模块搜索路径,Python 在导入期间将在其中的目录列表中寻找模块。最前面的空字符串
'' 是指当前目录:
- >>> sys.path
- ['', '/home/pobrien/Code',
- '/usr/local/lib/python2.2',
- '/usr/local/lib/python2.2/plat-linux2',
- '/usr/local/lib/python2.2/lib-tk',
- '/usr/local/lib/python2.2/lib-dynload',
- '/usr/local/lib/python2.2/site-packages']
modules 变量是一个字典,它将当前已装入的所有模块的名称映射到模块对象。如您所见,缺省情况下,Python 装入一些特定的模块:
- >>> sys.modules
- {'stat': <module 'stat' from '/usr/local/lib/python2.2/stat.pyc'>,
- '__future__': <module '__future__' from '/usr/local/lib/python2.2/__future__.pyc'>,
- 'copy_reg': <module 'copy_reg' from '/usr/local/lib/python2.2/copy_reg.pyc'>,
- 'posixpath': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>,
- 'UserDict': <module 'UserDict' from '/usr/local/lib/python2.2/UserDict.pyc'>,
- 'signal': <module 'signal' (built-in)>,
- 'site': <module 'site' from '/usr/local/lib/python2.2/site.pyc'>,
- '__builtin__': <module '__builtin__' (built-in)>,
- 'sys': <module 'sys' (built-in)>,
- 'posix': <module 'posix' (built-in)>,
- 'types': <module 'types' from '/usr/local/lib/python2.2/types.pyc'>,
- '__main__': <module '__main__' (built-in)>,
- 'exceptions': <module 'exceptions' (built-in)>,
- 'os': <module 'os' from '/usr/local/lib/python2.2/os.pyc'>,
- 'os.path': <module 'posixpath' from '/usr/local/lib/python2.2/posixpath.pyc'>}
让我们返回到关于 Python 关键字的问题。尽管帮助向我们显示了关键字列表,但事实证明一些帮助信息是硬编码的。关键字列表恰好是硬编码的,但毕竟它的自省程度不深。让我们研究一下,能否直接从 Python 标准库的某个模块中获取这个信息。如果在 Python 提示符下输入
help('modules keywords') ,则会看到如下信息:
- >>> help('modules keywords')
- Here is a list of matching modules. Enter any module name to get more help.
- keyword - Keywords (from "graminit.c")
看起来,keyword 模块好象包含关键字。在文本编辑器中打开keyword.py 文件,我们可以看到,Python 确实可以把关键字列表显式地用作keyword 模块的kwlist 属性。在keyword 模块的注释中,我们还可以看到,该模块是根据 Python 本身的源代码自动生成的,这可以保证其关键字列表是准确而完整的:
- >>> import keyword
- >>> keyword.kwlist
- ['and', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else',
- 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
- 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'yield']
尽管查找和导入模块相对容易,但要记住每个模块包含什么却不是这么简单。您并不希望总是必须查看源代码来找出答案。幸运的是,Python 提供了一种方法,可以使用内置的
dir() 函数来检查模块(以及其它对象)的内容。
dir() 函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则
dir() 返回当前作用域中的名称。让我们将
dir() 函数应用于
keyword 模块,并观察它揭示了什么:
- >>> dir(keyword)
- ['__all__', '__builtins__', '__doc__', '__file__', '__name__',
- 'iskeyword', 'keyword', 'kwdict', 'kwlist', 'main']
那么将它应用于我们先前讨论的
sys 模块会怎么样呢?
- >>> dir(sys)
- ['__displayhook__', '__doc__', '__excepthook__', '__name__', '__stderr__',
- '__stdin__', '__stdout__', '_getframe', 'argv', 'builtin_module_names',
- 'byteorder', 'copyright', 'displayhook', 'exc_info', 'exc_type', 'excepthook',
- 'exec_prefix', 'executable', 'exit', 'getdefaultencoding', 'getdlopenflags',
- 'getrecursionlimit', 'getrefcount', 'hexversion', 'last_traceback',
- 'last_type', 'last_value', 'maxint', 'maxunicode', 'modules', 'path',
- 'platform', 'prefix', 'ps1', 'ps2', 'setcheckinterval', 'setdlopenflags',
- 'setprofile', 'setrecursionlimit', 'settrace', 'stderr', 'stdin', 'stdout',
- 'version', 'version_info', 'warnoptions']
如果不带任何参数,则
dir() 返回当前作用域中的名称。请注意,因为我们先前导入了
keyword 和
sys ,所以它们出现在列表中。导入模块将把该模块的名称添加到当前作用域:
- >>> dir()
- ['__builtins__', '__doc__', '__name__', 'keyword', 'sys']
我们曾经提到
dir() 函数是内置函数,这意味着我们不必为了使用该函数而导入模块。不必做任何操作,Python 就可识别内置函数。现在,我们看到调用
dir() 后返回了这个名称
__builtins__ 。也许此处有连接。让我们在 Python 提示符下输入名称
__builtins__ ,并观察 Python 是否会告诉我们关于它的任何有趣的事情:
- >>> __builtins__
- <module '__builtin__' (built-in)>
因此
__builtins__ 看起来象是当前作用域中绑定到名为
__builtin__ 的模块对象的名称。(因为模块不是只有多个单一值的简单对象,所以 Python 改在尖括号中显示关于模块的信息。)注:如果您在磁盘上寻找
__builtin__.py 文件,将空手而归。这个特殊的模块对象是 Python 解释器凭空创建的,因为它包含着解释器始终可用的项。尽管看不到物理文件,但我们仍可以将
dir() 函数应用于这个对象,以观察所有内置函数、错误对象以及它所包含的几个杂项属性。
- >>> dir(__builtins__)
- ['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning',
- 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False',
- 'FloatingPointError', 'IOError', 'ImportError', 'IndentationError',
- 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError',
- 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError',
- 'OverflowError', 'OverflowWarning', 'ReferenceError', 'RuntimeError',
- 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError',
- 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError',
- 'UnboundLocalError', 'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
- 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__',
- 'abs', 'apply', 'bool', 'buffer', 'callable', 'chr', 'classmethod', 'cmp',
- 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict',
- 'dir', 'divmod', 'eval', 'execfile', 'exit', 'file', 'filter', 'float',
- 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int',
- 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list',
- 'locals', 'long', 'map', 'max', 'min', 'object', 'oct', 'open', 'ord', 'pow',
- 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round',
- 'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple', 'type', 'unichr',
- 'unicode', 'vars', 'xrange', 'zip']
dir() 函数适用于所有对象类型,包括字符串、整数、列表、元组、字典、函数、定制类、类实例和类方法。让我们将
dir() 应用于字符串对象,并观察 Python 返回什么。如您所见,即使简单的 Python 字符串也有许多属性:
- >>> dir('this is a string')
- ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__',
- '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__',
- '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mul__', '__ne__',
- '__new__', '__reduce__', '__repr__', '__rmul__', '__setattr__', '__str__',
- 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs',
- 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace',
- 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind',
- 'rindex', 'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip',
- 'swapcase', 'title', 'translate', 'upper', 'zfill']
自己尝试下列示例以观察它们返回什么。注:
# 字符标记注释的开始。Python 将忽略从注释开始部分到该行结束之间的所有内容:
- dir(42) # Integer (and the meaning of life)
- dir([]) # List (an empty list, actually)
- dir(()) # Tuple (also empty)
- dir({}) # Dictionary (ditto)
- dir(dir) # Function (functions are also objects)
为了说明 Python 自省能力的动态本质,让我们研究将
dir() 运用于定制类和一些类实例的示例。我们将以交互方式定义自己的类,创建一些类的实例,仅向其中一个实例添加唯一的属性,并观察 Python 能否一直保存所有这些。以下是结果:
- >>> class Person(object):
- ... """Person class."""
- ... def __init__(self, name, age):
- ... self.name = name
- ... self.age = age
- ... def intro(self):
- ... """Return an introduction."""
- ... return "Hello, my name is %s and I'm %s." % (self.name, self.age)
- ...
- >>> bob = Person("Robert", 35) # Create a Person instance
- >>> joe = Person("Joseph", 17) # Create another
- >>> joe.sport = "football" # Assign a new attribute to one instance
- >>> dir(Person) # Attributes of the Person class
- ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
- '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
- '__setattr__', '__str__', '__weakref__', 'intro']
- >>> dir(bob) # Attributes of bob
- ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
- '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
- '__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name']
- >>> dir(joe) # Note that joe has an additional attribute
- ['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__',
- '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__repr__',
- '__setattr__', '__str__', '__weakref__', 'age', 'intro', 'name', 'sport']
- >>> bob.intro() # Calling bob's intro method
- "Hello, my name is Robert and I'm 35."
- >>> dir(bob.intro) # Attributes of the intro method
- ['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__get__',
- '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
- '__repr__', '__setattr__', '__str__', 'im_class', 'im_func', 'im_self']
在许多
dir() 示例中,您可能会注意到的一个属性是
__doc__ 属性。这个属性是一个字符串,它包含了描述对象的注释。Python 称之为文档字符串或 docstring,以下是其工作原理。如果模块、类、方法或函数定义的第一条语句是字符串,那么该字符串会作为对象的
__doc__ 属性与该对象关联起来。例如,看一下
__builtins__ 对象的文档字符串。因为文档字符串通常包含嵌入的换行
\n ,我们将使用 Python 的
print 语句,以便输出更易于阅读:
- >>> print __builtins__.__doc__ # Module docstring
- Built-in functions, exceptions, and other objects.
- Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.
Python 甚至再次维持了在 Python shell 中以交互方式定义的类和方法上的文档字符串。让我们研究
Person 类及其
intro 方法的文档字符串:
- >>> Person.__doc__ # Class docstring
- 'Person class.'
- >>> Person.intro.__doc__ # Class method docstring
- 'Return an introduction.'
因为文档字符串提供了如此有价值的信息,所以许多 Python 开发环境都有自动显示对象的文档字符串的方法。让我们再看一个
dir() 函数的文档字符串:
- >>> print dir.__doc__ # Function docstring
- dir([object]) -> list of strings
- Return an alphabetized list of names comprising (some of) the attributes
- of the given object, and of attributes reachable from it:
- No argument: the names in the current scope.
- Module object: the module attributes.
- Type or class object: its attributes, and recursively the attributes of
- its bases.
- Otherwise: its attributes, its class's attributes, and recursively the
- attributes of its class's base classes.
我们好几次提到了“对象(object)”这个词,但一直没有真正定义它。编程环境中的对象很象现实世界中的对象。实际的对象有一定的形状、大小、重量和其它特征。实际的对象还能够对其环境进行响应、与其它对象交互或执行任务。计算机中的对象试图模拟我们身边现实世界中的对象,包括象文档、日程表和业务过程这样的抽象对象。
类似于实际的对象,几个计算机对象可能共享共同的特征,同时保持它们自己相对较小的变异特征。想一想您在书店中看到的书籍。书籍的每个物理副本都可能有污迹、几张破损的书页或唯一的标识号。尽管每本书都是唯一的对象,但都拥有相同标题的每本书都只是原始模板的实例,并保留了原始模板的大多数特征。
对于面向对象的类和类实例也是如此。例如,可以看到每个 Python 字符串都被赋予了一些属性,
dir() 函数揭示了这些属性。在前一个示例中,我们定义了自己的
Person 类,它担任创建个别 Person 实例的模板,每个实例都有自己的 name 和 age 值,同时共享自我介绍的能力。这就是面向对象。
于是在计算机术语中,对象是拥有标识和值的事物,属于特定类型、具有特定特征和以特定方式执行操作。并且,对象从一个或多个父类继承了它们的许多属性。除了关键字和特殊符号(象运算符,如
+ 、
- 、
* 、
** 、
/ 、
% 、
< 、
> 等)外,Python 中的所有东西都是对象。Python 具有一组丰富的对象类型:字符串、整数、浮点、列表、元组、字典、函数、类、类实例、模块、文件等。
当您有一个任意的对象(也许是一个作为参数传递给函数的对象)时,可能希望知道一些关于该对象的情况。在本节中,我们将向您展示如何让 Python 对象回答如下问题:
并非所有对象都有名称,但那些有名称的对象都将名称存储在其
__name__ 属性中。注:名称是从对象而不是引用该对象的变量中派生的。下面这个示例着重说明了这种区别:
- $ python
- Python 2.2.2 (#1, Oct 28 2002, 17:22:19)
- [GCC 3.2 (Mandrake Linux 9.0 3.2-1mdk)] on linux2
- Type "help", "copyright", "credits" or "license" for more information.
- >>> dir() # The dir() function
- ['__builtins__', '__doc__', '__name__']
- >>> directory = dir # Create a new variable
- >>> directory() # Works just like the original object
- ['__builtins__', '__doc__', '__name__', 'directory']
- >>> dir.__name__ # What's your name?
- 'dir'
- >>> directory.__name__ # My name is the same
- 'dir'
- >>> __name__ # And now for something completely different
- '__main__'
模块拥有名称,Python 解释器本身被认为是顶级模块或主模块。当以交互的方式运行 Python 时,局部
__name__ 变量被赋予值
'__main__' 。同样地,当从命令行执行 Python 模块,而不是将其导入另一个模块时,其
__name__ 属性被赋予值
'__main__' ,而不是该模块的实际名称。这样,模块可以查看其自身的
__name__ 值来自行确定它们自己正被如何使用,是作为另一个程序的支持,还是作为从命令行执行的主应用程序。因此,下面这条惯用的语句在 Python 模块中是很常见的:
- if __name__ == '__main__':
- # Do something appropriate here, like calling a
- # main() function defined elsewhere in this module.
- main()
- else:
- # Do nothing. This module has been imported by another
- # module that wants to make use of the functions,
- # classes and other useful bits it has defined.
type() 函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。它通过返回类型对象来做到这一点,可以将这个类型对象与
types 模块中定义的类型相比较:
- >>> import types
- >>> print types.__doc__
- Define names for all type symbols known in the standard interpreter.
- Types that are part of optional modules (e.g. array) are not listed.
- >>> dir(types)
- ['BufferType', 'BuiltinFunctionType', 'BuiltinMethodType', 'ClassType',
- 'CodeType', 'ComplexType', 'DictProxyType', 'DictType', 'DictionaryType',
- 'EllipsisType', 'FileType', 'FloatType', 'FrameType', 'FunctionType',
- 'GeneratorType', 'InstanceType', 'IntType', 'LambdaType', 'ListType',
- 'LongType', 'MethodType', 'ModuleType', 'NoneType', 'ObjectType', 'SliceType',
- 'StringType', 'StringTypes', 'TracebackType', 'TupleType', 'TypeType',
- 'UnboundMethodType', 'UnicodeType', 'XRangeType', '__builtins__', '__doc__',
- '__file__', '__name__']
- >>> s = 'a sample string'
- >>> type(s)
- <type 'str'>
- >>> if type(s) is types.StringType: print "s is a string"
- ...
- s is a string
- >>> type(42)
- <type 'int'>
- >>> type([])
- <type 'list'>
- >>> type({})
- <type 'dict'>
- >>> type(dir)
- <type 'builtin_function_or_method'>
先前我们说过,每个对象都有标识、类型和值。值得注意的是,可能有多个变量引用同一对象,同样地,变量可以引用看起来相似(有相同的类型和值),但拥有截然不同标识的多个对象。当更改对象时(如将某一项添加到列表),这种关于对象标识的概念尤其重要,如在下面的示例中,
blist 和
clist 变量引用同一个列表对象。正如您在示例中所见,
id() 函数给任何给定对象返回唯一的标识符:
- >>> print id.__doc__
- id(object) -> integer
- Return the identity of an object. This is guaranteed to be unique among
- simultaneously existing objects. (Hint: it's the object's memory address.)
- >>> alist = [1, 2, 3]
- >>> blist = [1, 2, 3]
- >>> clist = blist
- >>> clist
- [1, 2, 3]
- >>> blist
- [1, 2, 3]
- >>> alist
- [1, 2, 3]
- >>> id(alist)
- 145381412
- >>> id(blist)
- 140406428
- >>> id(clist)
- 140406428
- >>> alist is blist # Returns 1 if True, 0 if False
- 0
- >>> blist is clist # Ditto
- 1
- >>> clist.append(4) # Add an item to the end of the list
- >>> clist
- [1, 2, 3, 4]
- >>> blist # Same, because they both point to the same object
- [1, 2, 3, 4]
- >>> alist # This one only looked the same initially
- [1, 2, 3]
我们已经看到对象拥有属性,并且
dir() 函数会返回这些属性的列表。但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由
hasattr() 和
getattr() 函数来完成,如本例所示:
- >>> print hasattr.__doc__
- hasattr(object, name) -> Boolean
- Return whether the object has an attribute with the given name.
- (This is done by calling getattr(object, name) and catching exceptions.)
- >>> print getattr.__doc__
- getattr(object, name[, default]) -> value
- Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
- When a default argument is given, it is returned when the attribute doesn't
- exist; without it, an exception is raised in that case.
- >>> hasattr(id, '__doc__')
- 1
- >>> print getattr(id, '__doc__')
- id(object) -> integer
- Return the identity of an object. This is guaranteed to be unique among
- simultaneously existing objects. (Hint: it's the object's memory address.)
可以调用表示潜在行为(函数和方法)的对象。可以用
callable() 函数测试对象的可调用性:
- >>> print callable.__doc__
- callable(object) -> Boolean
- Return whether the object is callable (i.e., some kind of function).
- Note that classes are callable, as are instances with a __call__() method.
- >>> callable('a string')
- 0
- >>> callable(dir)
- 1
在
type() 函数提供对象的类型时,还可以使用
isinstance() 函数测试对象,以确定它是否是某个特定类型或定制类的实例:
- >>> print isinstance.__doc__
- isinstance(object, class-or-type-or-tuple) -> Boolean
- Return whether an object is an instance of a class or of a subclass thereof.
- With a type as second argument, return whether that is the object's type.
- The form using a tuple, isinstance(x, (A, B, ...)), is a shortcut for
- isinstance(x, A) or isinstance(x, B) or ... (etc.).
- >>> isinstance(42, str)
- 0
- >>> isinstance('a string', int)
- 0
- >>> isinstance(42, int)
- 1
- >>> isinstance('a string', str)
- 1
我们先前提到过,定制类的实例从该类继承了属性。在类这一级别,可以根据一个类来定义另一个类,同样地,这个新类会按照层次化的方式继承属性。Python 甚至支持多重继承,多重继承意味着可以用多个父类来定义一个类,这个新类继承了多个父类。
issubclass() 函数使我们可以查看一个类是不是继承了另一个类:
- >>> print issubclass.__doc__
- issubclass(C, B) -> Boolean
- Return whether class C is a subclass (i.e., a derived class) of class B.
- >>> class SuperHero(Person): # SuperHero inherits from Person...
- ... def intro(self): # but with a new SuperHero intro
- ... """Return an introduction."""
- ... return "Hello, I'm SuperHero %s and I'm %s." % (self.name, self.age)
- ...
- >>> issubclass(SuperHero, Person)
- 1
- >>> issubclass(Person, SuperHero)
- 0
- >>>
让我们将上一节中讨论的几种检查技术结合起来。为了做到这一点,要定义自己的函数 —
interrogate() ,它打印有关传递给它的任何对象的各种信息。以下是代码,后面是其用法的几个示例:
- >>> def interrogate(item):
- ... """Print useful information about item."""
- ... if hasattr(item, '__name__'):
- ... print "NAME: ", item.__name__
- ... if hasattr(item, '__class__'):
- ... print "CLASS: ", item.__class__.__name__
- ... print "ID: ", id(item)
- ... print "TYPE: ", type(item)
- ... print "VALUE: ", repr(item)
- ... print "CALLABLE:",
- ... if callable(item):
- ... print "Yes"
- ... else:
- ... print "No"
- ... if hasattr(item, '__doc__'):
- ... doc = getattr(item, '__doc__')
- ... doc = doc.strip() # Remove leading/trailing whitespace.
- ... firstline = doc.split('\n')[0]
- ... print "DOC: ", firstline
- ...
- >>> interrogate('a string') # String object
- CLASS: str
- ID: 141462040
- TYPE: <type 'str'>
- VALUE: 'a string'
- CALLABLE: No
- DOC: str(object) -> string
- >>> interrogate(42) # Integer object
- CLASS: int
- ID: 135447416
- TYPE: <type 'int'>
- VALUE: 42
- CALLABLE: No
- DOC: int(x[, base]) -> integer
- >>> interrogate(interrogate) # User-defined function object
- NAME: interrogate
- CLASS: function
- ID: 141444892
- TYPE: <type 'function'>
- VALUE: <function interrogate at 0x86e471c>
- CALLABLE: Yes
- DOC: Print useful information about item.
正如您在最后一个示例中所看到的,interrogate() 函数甚至可以应用于它本身。您没有再比它更具“自省性”的工具了。
谁知道自省可以变得这么简单而又如此有价值?可是,我在结束时必须提出警告:不要将自省的结果误认为是万能的。有经验的 Python 程序员知道:他们不知道的东西总是比已知的更多,因此根本不可能是万能的。编程行为产生的问题多于答案。关于 Python 的唯一优点(正如我们今天在本文中所见)是它确实回答了人们的问题。至于我本人,觉得您不必因为我帮助您了解了这些 Python 必须提供的内容而酬谢我。用 Python 编程自有回报。我从爱好 Python 的同伴处获得的帮助也是不计报酬的。