分享

Python 自省指南

 yfmine 2007-02-22



2002 年 12 月 26 日

自省揭示了关于程序对象的有用信息。Python 是动态的面向对象的编程语言,提供了很棒的自省支持。本文展示了该语言的许多能力,从最基本形式的帮助到较为高级形式的调查。

检查 Python 对象

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

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

对于面向对象的类和类实例也是如此。例如,可以看到每个 Python 字符串都被赋予了一些属性,dir() 函数揭示了这些属性。在前一个示例中,我们定义了自己的 Person 类,它担任创建个别 Person 实例的模板,每个实例都有自己的 name 和 age 值,同时共享自我介绍的能力。这就是面向对象。

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

当您有一个任意的对象(也许是一个作为参数传递给函数的对象)时,可能希望知道一些关于该对象的情况。在本节中,我们将向您展示如何让 Python 对象回答如下问题:

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

名称

并非所有对象都有名称,但那些有名称的对象都将名称存储在其 __name__ 属性中。注:名称是从对象而不是引用该对象的变量中派生的。下面这个示例着重说明了这种区别:



清单 27. 名称中有什么?
$ 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 模块中是很常见的:



清单 28. 用于执行或导入的测试
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 模块中定义的类型相比较:



清单 29. 我是您的类型吗?
>>> 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‘>
                                    

标识

先前我们说过,每个对象都有标识、类型和值。值得注意的是,可能有多个变量引用同一对象,同样地,变量可以引用看起来相似(有相同的类型和值),但拥有截然不同标识的多个对象。当更改对象时(如将某一项添加到列表),这种关于对象标识的概念尤其重要,如在下面的示例中,blistclist 变量引用同一个列表对象。正如您在示例中所见,id() 函数给任何给定对象返回唯一的标识符:



清单 30. 目的地……
>>> 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() 函数来完成,如本例所示:



清单 31. 具有一个属性;获得一个属性
>>> 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() 函数测试对象的可调用性:



清单 32. 您能为我做些事情吗?
>>> 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() 函数测试对象,以确定它是否是某个特定类型或定制类的实例:



清单 33. 您是那些实例中的一个吗?
>>> 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() 函数使我们可以查看一个类是不是继承了另一个类:



清单 34. 您是我母亲吗?
>>> 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(),它打印有关传递给它的任何对象的各种信息。以下是代码,后面是其用法的几个示例:



清单 35. 谁也没料到它
>>> 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 的同伴处获得的帮助也是不计报酬的。

上一页



参考资料



关于作者

作者照片

Patrick O‘Brien 是一位 Python 程序员、顾问和培训人员。他创建了 PyCrust,并且还是 PythonCard 项目的开发人员。他最近带领 PyPerSyst 团队将 Prevayler 移植到了 Python,并且继续引领着这个项目向新的令人感兴趣的领域前进。可以在 Orbtech 网站了解更多有关作者和他工作方面的情况,或者可以通过 pobrien@ 与他联系。



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多