离线下载
PDF版 ePub版

老齐 · 更新于 2018-11-28 11:00:43

自省

特别说明,这一讲的内容不是我写的,是我从《Python自省指南》抄录过来的,当然,为了适合本教程,我在某些地方做了修改或者重写。

什么是自省?

在日常生活中,自省(introspection)是一种自我检查行为。自省是指对某人自身思想、情绪、动机和行为的检查。伟大的哲学家苏格拉底将生命中的大部分时间用于自我检查,并鼓励他的雅典朋友们也这样做。他甚至对自己作出了这样的要求:“未经自省的生命不值得存在。”无独有偶,在中国《论语》中,也有这样的名言:“吾日三省吾身”。显然,自省对个人成长多么重要呀。

在计算机编程中,自省是指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。自省向程序员提供了极大的灵活性和控制力。一旦您使用了支持自省的编程语言,就会产生类似这样的感觉:“未经检查的对象不值得实例化。”

整个 Python 语言对自省提供了深入而广泛的支持。实际上,很难想象假如 Python 语言没有其自省特性是什么样子。

学完这节,你就能够轻松洞察到 Python 对象的“灵魂”。

在深入研究更高级的技术之前,我们尽可能用最普通的方式来研究 Python 自省。有些读者甚至可能会争论说:我们开始时所讨论的特性不应称之为“自省”。我们必须承认,它们是否属于自省的范畴还有待讨论。但从本节的意图出发,我们所关心的只是找出有趣问题的答案。

现在让我们以交互方式使用 Python 来开始研究。这是前面已经在使用的一种方式。

联机帮助

在交互模式下,用 help 向 Python 请求帮助。

>>> help()

Welcome to Python 2.7!  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://docs.python.org/2.7/tutorial/.

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> 

这时候就进入了联机帮助状态,根据提示输入 keywords

help> keywords

Here is a list of the Python keywords.  Enter any keyword to get more help.

and                 elif                if                  print
as                  else                import              raise
assert              except              in                  return
break               exec                is                  try
class               finally             lambda              while
continue            for                 not                 with
def                 from                or                  yield
del                 global              pass                

现在显示出了 Python 关键词的列表。依照说明亦步亦趋,输入每个关键词,就能看到那个关键词的相关文档。这里就不展示输入的结果了。读者可以自行尝试。要记住,如果从文档说明界面返回到帮助界面,需要按 q 键。

这样,我们能够得到联机帮助。从联机帮助状态退回到 Python 的交互模式,使用 quit 命令。

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.
>>> 

联机帮助实用程序会显示关于各种主题或特定对象的信息。

帮助实用程序很有用,并确实利用了 Python 的自省能力。但仅仅使用帮助不会揭示帮助是如何获得其信息的。而且,因为我们的目的是揭示 Python 自省的所有秘密,所以我们必须迅速地跳出对帮助实用程序的讨论。

在结束关于帮助的讨论之前,让我们用它来获得一个可用模块的列表。

模块只是包含 Python 代码的文本文件,其名称后缀是 .py ,关于模块,本教程会在后面有专门的讲解。如果在 Python 提示符下输入 help('modules') ,或在 help 提示符下输入 modules,则会看到一长列可用模块,类似于下面所示的部分列表。自己尝试它以观察您的系统中有哪些可用模块,并了解为什么会认为 Python 是“自带电池”的(自带电池,这是一个比喻,就是说 Python 在被安装时,就带了很多模块,这些模块是你以后开发中会用到的,比喻成电池,好比开发的助力工具),或者说是 Python 一被安装,就已经包含有的模块,不用我们费力再安装了。

>>> help("modules")

Please wait a moment while I gather a list of all available modules...
ANSI                _threading_local    gnomekeyring        repr
BaseHTTPServer      _warnings           gobject             requests
MySQLdb             chardet             lsb_release         sre_parse
......(此处省略一些)
PyQt4               codeop              markupbase          stringprep
Queue               collections         marshal             strop
ScrolledText        colorama            math                struct
......(省略其它的模块)
Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose descriptions contain the word "spam".

因为太多,无法全部显示。你可以子线观察一下,是不是有我们前面已经用过的那个 mathrandom 模块呢?

如果是在 Python 交互模式 >>> 下,比如要得到有关 math 模块的更多帮助,可以输入 >>> help("math"),如果是在帮助模式 help> 下,直接输入 >math 就能得到关于 math 模块的详细信息。简直太贴心了。

dir()

尽管查找和导入模块相对容易,但要记住每个模块包含什么却不是这么简单。你或许并不希望总是必须查看源代码来找出答案。幸运的是,Python 提供了一种方法,可以使用内置的 dir() 函数来检查模块(以及其它对象)的内容。

其实,这个东西我们已经一直在使用。

dir() 函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中(这里冒出来一个新名词:“作用域”,暂且不用管它,后面会详解,你就姑且理解为某个范围吧)的名称。让我们将 dir() 函数应用于 keyword 模块,并观察它揭示了什么:

>>> import keyword
>>> dir(keyword)
['__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'iskeyword', 'kwlist', 'main']

如果不带任何参数,则 dir() 返回当前作用域中的名称。请注意,因为我们先前导入了 keyword ,所以它们出现在列表中。导入模块将把该模块的名称添加到当前作用域:

>>> dir()
['GFileDescriptorBased', 'GInitiallyUnowned', 'GPollableInputStream', 'GPollableOutputStream', '__builtins__', '__doc__', '__name__', '__package__', 'keyword']
>>> import math
>>> dir()
['GFileDescriptorBased', 'GInitiallyUnowned', 'GPollableInputStream', 'GPollableOutputStream', '__builtins__', '__doc__', '__name__', '__package__', 'keyword', 'math']

dir() 函数是内置函数,这意味着我们不必为了使用该函数而导入模块。不必做任何操作,Python 就可识别内置函数。

再观察,看到调用 dir() 后返回了这个名称 __builtins__ 。也许此处有连接。让我们在 Python 提示符下输入名称 __builtins__ ,并观察 Python 是否会告诉我们关于它的任何有趣的事情:

>>> __builtins__
<module '__builtin__' (built-in)>

因此 __builtins__ 看起来象是当前作用域中绑定到名为 __builtin__ 的模块对象的名称。(因为模块不是只有多个单一值的简单对象,所以 Python 改在尖括号中显示关于模块的信息。)

注:如果您在磁盘上寻找 __builtin__.py 文件,将空手而归。这个特殊的模块对象是 Python 解释器凭空创建的,因为它包含着解释器始终可用的项。尽管看不到物理文件,但我们仍可以将 dir() 函数应用于这个对象,以观察所有内置函数、错误对象以及它所包含的几个杂项属性。

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '_', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs', 'all', 'any', 'apply', 'ascii', 'basestring', 'bin', 'bool', 'buffer', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'cmp', 'coerce', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'execfile', 'exit', 'file', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'memoryview', 'min', 'next', 'ngettext', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

dir() 函数适用于所有对象类型,包括字符串、整数、列表、元组、字典、函数、定制类、类实例和类方法(不理解的对象类型,会在随后的教程中讲解)。例如将 dir() 应用于字符串对象,如您所见,即使简单的 Python 字符串也有许多属性(这是前面已经知道的了,权当复习)

>>> dir("You raise me up")
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

读者可以尝试一下其它的对象类型,观察返回结果,如:dir(42),dir([]),dir(()),dir({}),dir(dir)`。

文档字符串

在许多 dir() 示例中,您可能会注意到的一个属性是 __doc__ 属性。这个属性是一个字符串,它包含了描述对象的注释。Python 称之为文档字符串或 docstring(这个内容,会在下一部分中讲解如何自定义设置)。

如果模块、类、方法或函数定义的第一条语句是字符串,那么该字符串会作为对象的 __doc__ 属性与该对象关联起来。例如,看一下 str 类型对象的文档字符串。因为文档字符串通常包含嵌入的换行 \n ,我们将使用 Python 的 print 语句,以便输出更易于阅读:

>>> print str.__doc__
str(object='') -> string

Return a nice string representation of the object.
If the argument is a string, the return value is the same object.

检查 Python 对象

前面已经好几次提到了“对象(object)”这个词,但一直没有真正定义它。编程环境中的对象很象现实世界中的对象。实际的对象有一定的形状、大小、重量和其它特征。实际的对象还能够对其环境进行响应、与其它对象交互或执行任务。计算机中的对象试图模拟我们身边现实世界中的对象,包括象文档、日程表和业务过程这样的抽象对象。

其实,我总觉得把 object 翻译成对象,让人感觉很没有具象的感觉,因为在汉语里面,对象是一个很笼统的词汇。另外一种翻译,流行于台湾,把它称为“物件”,倒是挺不错的理解。当然,名词就不纠缠了,关键是理解内涵。关于面向对象编程,可以阅读维基百科的介绍——面向对象程序设计——先了解大概。

类似于实际的对象,几个计算机对象可能共享共同的特征,同时保持它们自己相对较小的变异特征。想一想您在书店中看到的书籍。书籍的每个物理副本都可能有污迹、几张破损的书页或唯一的标识号。尽管每本书都是唯一的对象,但都拥有相同标题的每本书都只是原始模板的实例,并保留了原始模板的大多数特征。

对于面向对象的类和类实例也是如此。例如,可以看到每个 Python 符串都被赋予了一些属性, dir() 函数揭示了这些属性。

于是在计算机术语中,对象是拥有标识和值的事物,属于特定类型、具有特定特征和以特定方式执行操作。并且,对象从一个或多个父类继承了它们的许多属性。除了关键字和特殊符号(象运算符,如 + 、 - 、 * 、 ** 、 / 、 % 、 < 、 > 等)外,Python 中的所有东西都是对象。Python 具有一组丰富的对象类型:字符串、整数、浮点、列表、元组、字典、函数、类、类实例、模块、文件等。

当您有一个任意的对象(也许是一个作为参数传递给函数的对象)时,可能希望知道一些关于该对象的情况。如希望 Python 告诉我们:

  • 对象的名称是什么?
  • 这是哪种类型的对象?
  • 对象知道些什么?
  • 对象能做些什么?
  • 对象的父对象是谁?

名称

并非所有对象都有名称,但那些有名称的对象都将名称存储在其 __name__ 属性中。注:名称是从对象而不是引用该对象的变量中派生的。

>>> dir()    #dir() 函数
['GFileDescriptorBased', 'GInitiallyUnowned', 'GPollableInputStream', 'GPollableOutputStream', '__builtins__', '__doc__', '__name__', '__package__', 'keyword', 'math']
>>> directory = dir    #新变量
>>> directory()        #跟 dir() 一样的结果
['GFileDescriptorBased', 'GInitiallyUnowned', 'GPollableInputStream', 'GPollableOutputStream', '__builtins__', '__doc__', '__name__', '__package__', 'directory', 'keyword', 'math']
>>> dir.__name__       #dir() 的名字
'dir'
>>> directory.__name__
'dir'

>>> __name__          #这是不一样的   
'__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)
['BooleanType', 'BufferType', 'BuiltinFunctionType', 'BuiltinMethodType', 'ClassType', 'CodeType', 'ComplexType', 'DictProxyType', 'DictType', 'DictionaryType', 'EllipsisType', 'FileType', 'FloatType', 'FrameType', 'FunctionType', 'GeneratorType', 'GetSetDescriptorType', 'InstanceType', 'IntType', 'LambdaType', 'ListType', 'LongType', 'MemberDescriptorType', 'MethodType', 'ModuleType', 'NoneType', 'NotImplementedType', 'ObjectType', 'SliceType', 'StringType', 'StringTypes', 'TracebackType', 'TupleType', 'TypeType', 'UnboundMethodType', 'UnicodeType', 'XRangeType', '__builtins__', '__doc__', '__file__', '__name__', '__package__']
>>> p = "I love Python"
>>> type(p)
<type 'str'>
>>> if type(p) is types.StringType:
...     print "p is a string"
... 
p 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
>>> id(alist)
2979691052L
>>> id(blist)
2993911916L
>>> id(clist)
2993911916L
>>> alist is blist
False
>>> blist is clist
True
>>> clist.append(4)
>>> clist
[1, 2, 3, 4]
>>> blist
[1, 2, 3, 4]
>>> alist
[1, 2, 3]

如果对上面的操作还有疑惑,可以回到前面复习有关深拷贝和浅拷贝的知识。

属性

对象拥有属性,并且 dir() 函数会返回这些属性的列表。但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr() 和 getattr() 函数来完成.

>>> print hasattr.__doc__
hasattr(object, name) -> bool

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__')
True

>>> 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) -> bool

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")
False
>>> callable(dir)
True

实例

这个名词还很陌生,没关系,先看看,混个脸熟,以后会经常用到。

在 type() 函数提供对象的类型时,还可以使用 isinstance() 函数测试对象,以确定它是否是某个特定类型或定制类的实例:

>>> print isinstance.__doc__
isinstance(object, class-or-type-or-tuple) -> bool

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)
False
>>> isinstance("python", str)
True

子类

关于类的问题,有一个“继承”概念,有继承就有父子问题,这是在现实生活中很正常的,在编程语言中也是如此。虽然这是后面要说的,但是,为了本讲内容的完整,也姑且把这个内容放在这里。读者可以不看,留着以后看也行。我更建议还是阅读一下,有个印象。

在类这一级别,可以根据一个类来定义另一个类,同样地,这个新类会按照层次化的方式继承属性。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

Python 文档

文档,这个词语在经常在程序员的嘴里冒出来,有时候他们还经常以文档有没有或者全不全为标准来衡量一个软件项目是否高大上。那么,软件中的文档是什么呢?有什么要求呢?Python 文档又是什么呢?文档有什么用呢?

文档很重要。独孤九剑的剑诀、易筋经的心法、写着辟邪剑谱的袈裟,这些都是文档。连那些大牛人都要这些文档,更何况我们呢?所以,文档是很重要的。

文档,说白了就是用 word(这个最多了)等(注意这里的等,把不常用的工具都等掉了,包括我编辑文本时用的 vim 工具)文本编写工具写成的包含文本内容但不限于文字的文件。有点啰嗦,啰嗦的目的是为了严谨,呵呵。最好还是来一个更让人信服的定义,当然是来自维基百科。

软件文档或者源代码文档是指与软件系统及其软件工程过程有关联的文本实体。文档的类型包括软件需求文档,设计文档,测试文档,用户手册等。其中的需求文档,设计文档和测试文档一般是在软件开发过程中由开发者写就的,而用户手册等非过程类文档是由专门的非技术类写作人员写就的。

早期的软件文档主要指的是用户手册,根据 Barker 的定义,文档是用来对软件系统界面元素的设计、规划和实现过程的记录,以此来增强系统的可用性。而 Forward 则认为软件文档是被软件工程师之间用作沟通交流的一种方式,沟通的信息主要是有关所开发的软件系统。Parnas 则强调文档的权威性,他认为文档应该提供对软件系统的精确描述。

综上,我们可以将软件文档定义为:

1.文档是一种对软件系统的书面描述; 2.文档应当精确地描述软件系统; 3.软件文档是软件工程师之间用作沟通交流的一种方式; 4.文档的类型有很多种,包括软件需求文档,设计文档,测试文档,用户手册等; 5.文档的呈现方式有很多种,可以是传统的书面文字形式或图表形式,也可是动态的网页形式

那么这里说的 Python 文档指的是什么呢?一个方面就是每个学习者要学习 Python,Python 的开发者们(他们都是大牛)给我们这些小白提供了什么东西没有?能够让我们给他们这些大牛沟通,理解 Python 中每个函数、指令等的含义和用法呢?

有。大牛就是大牛,他们准备了,而且还不止一个。

真诚的敬告所有看本教程的诸位,要想获得编程上的升华,看文档是必须的。文档胜过了所有的教程和所有的老师以及所有的大牛。为什么呢?其中原因,都要等待看官看懂了之后,有了体会感悟之后才能明白。

Python 文档的网址:https://docs.python.org/2/,这是 Python2.x,从这里也可以找到 Python3.x 的文档。

当然,除了看官方文档之外,自己写的东西也可以写上文档。这个先不要着急,我们会在后续的学习中看到。


总目录   |   上节:练习   |   下节:函数(1)

如果你认为有必要打赏我,请通过支付宝:qiwsir@126.com,不胜感激。

上一篇: 练习 下一篇: 函数(1)