|
2002 年 12 月 26 日
自省揭示了關(guān)于程序?qū)ο蟮挠杏眯畔?。Python 是動態(tài)的面向?qū)ο蟮木幊陶Z言,提供了很棒的自省支持。本文展示了該語言的許多能力,從最基本形式的幫助到較為高級形式的調(diào)查。
檢查 Python 對象
我們好幾次提到了“對象(object)”這個詞,但一直沒有真正定義它。編程環(huán)境中的對象很象現(xiàn)實(shí)世界中的對象。實(shí)際的對象有一定的形狀、大小、重量和其它特征。實(shí)際的對象還能夠?qū)ζ洵h(huán)境進(jìn)行響應(yīng)、與其它對象交互或執(zhí)行任務(wù)。計算機(jī)中的對象試圖模擬我們身邊現(xiàn)實(shí)世界中的對象,包括象文檔、日程表和業(yè)務(wù)過程這樣的抽象對象。
類似于實(shí)際的對象,幾個計算機(jī)對象可能共享共同的特征,同時保持它們自己相對較小的變異特征。想一想您在書店中看到的書籍。書籍的每個物理副本都可能有污跡、幾張破損的書頁或唯一的標(biāo)識號。盡管每本書都是唯一的對象,但都擁有相同標(biāo)題的每本書都只是原始模板的實(shí)例,并保留了原始模板的大多數(shù)特征。
對于面向?qū)ο蟮念惡皖悓?shí)例也是如此。例如,可以看到每個 Python 字符串都被賦予了一些屬性,dir() 函數(shù)揭示了這些屬性。在前一個示例中,我們定義了自己的 Person 類,它擔(dān)任創(chuàng)建個別 Person 實(shí)例的模板,每個實(shí)例都有自己的 name 和 age 值,同時共享自我介紹的能力。這就是面向?qū)ο蟆?
于是在計算機(jī)術(shù)語中,對象是擁有標(biāo)識和值的事物,屬于特定類型、具有特定特征和以特定方式執(zhí)行操作。并且,對象從一個或多個父類繼承了它們的許多屬性。除了關(guān)鍵字和特殊符號(象運(yùn)算符,如 + 、- 、* 、** 、/ 、% 、< 、> 等)外,Python 中的所有東西都是對象。Python 具有一組豐富的對象類型:字符串、整數(shù)、浮點(diǎn)、列表、元組、字典、函數(shù)、類、類實(shí)例、模塊、文件等。
當(dāng)您有一個任意的對象(也許是一個作為參數(shù)傳遞給函數(shù)的對象)時,可能希望知道一些關(guān)于該對象的情況。在本節(jié)中,我們將向您展示如何讓 Python 對象回答如下問題:
- 對象的名稱是什么?
- 這是哪種類型的對象?
- 對象知道些什么?
- 對象能做些什么?
- 對象的父對象是誰?
名稱
并非所有對象都有名稱,但那些有名稱的對象都將名稱存儲在其 __name__ 屬性中。注:名稱是從對象而不是引用該對象的變量中派生的。下面這個示例著重說明了這種區(qū)別:
清單 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 解釋器本身被認(rèn)為是頂級模塊或主模塊。當(dāng)以交互的方式運(yùn)行 Python 時,局部 __name__ 變量被賦予值 ‘__main__‘ 。同樣地,當(dāng)從命令行執(zhí)行 Python 模塊,而不是將其導(dǎo)入另一個模塊時,其 __name__ 屬性被賦予值 ‘__main__‘ ,而不是該模塊的實(shí)際名稱。這樣,模塊可以查看其自身的 __name__ 值來自行確定它們自己正被如何使用,是作為另一個程序的支持,還是作為從命令行執(zhí)行的主應(yīng)用程序。因此,下面這條慣用的語句在 Python 模塊中是很常見的:
清單 28. 用于執(zhí)行或?qū)氲臏y試
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() 函數(shù)有助于我們確定對象是字符串還是整數(shù),或是其它類型的對象。它通過返回類型對象來做到這一點(diǎn),可以將這個類型對象與 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(chǎn) 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‘>
|
標(biāo)識
先前我們說過,每個對象都有標(biāo)識、類型和值。值得注意的是,可能有多個變量引用同一對象,同樣地,變量可以引用看起來相似(有相同的類型和值),但擁有截然不同標(biāo)識的多個對象。當(dāng)更改對象時(如將某一項添加到列表),這種關(guān)于對象標(biāo)識的概念尤其重要,如在下面的示例中,blist 和 clist 變量引用同一個列表對象。正如您在示例中所見,id() 函數(shù)給任何給定對象返回唯一的標(biāo)識符:
清單 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]
|
屬性
我們已經(jīng)看到對象擁有屬性,并且 dir() 函數(shù)會返回這些屬性的列表。但是,有時我們只想測試一個或多個屬性是否存在。如果對象具有我們正在考慮的屬性,那么通常希望只檢索該屬性。這個任務(wù)可以由 hasattr() 和 getattr() 函數(shù)來完成,如本例所示:
清單 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.)
|
可調(diào)用
可以調(diào)用表示潛在行為(函數(shù)和方法)的對象??梢杂?callable() 函數(shù)測試對象的可調(diào)用性:
清單 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(chǎn) string‘)
0
>>> callable(dir)
1
|
實(shí)例
在 type() 函數(shù)提供對象的類型時,還可以使用 isinstance() 函數(shù)測試對象,以確定它是否是某個特定類型或定制類的實(shí)例:
清單 33. 您是那些實(shí)例中的一個嗎?
>>> 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(chǎn) string‘, int)
0
>>> isinstance(42, int)
1
>>> isinstance(‘a(chǎn) string‘, str)
1
|
子類
我們先前提到過,定制類的實(shí)例從該類繼承了屬性。在類這一級別,可以根據(jù)一個類來定義另一個類,同樣地,這個新類會按照層次化的方式繼承屬性。Python 甚至支持多重繼承,多重繼承意味著可以用多個父類來定義一個類,這個新類繼承了多個父類。issubclass() 函數(shù)使我們可以查看一個類是不是繼承了另一個類:
清單 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
>>>
|
檢查時間
讓我們將上一節(jié)中討論的幾種檢查技術(shù)結(jié)合起來。為了做到這一點(diǎn),要定義自己的函數(shù) — interrogate() ,它打印有關(guān)傳遞給它的任何對象的各種信息。以下是代碼,后面是其用法的幾個示例:
清單 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(chǎn) string‘) # String object
CLASS: str
ID: 141462040
TYPE: <type ‘str‘>
VALUE: ‘a(chǎn) 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() 函數(shù)甚至可以應(yīng)用于它本身。您沒有再比它更具“自省性”的工具了。
結(jié)束語
誰知道自省可以變得這么簡單而又如此有價值?可是,我在結(jié)束時必須提出警告:不要將自省的結(jié)果誤認(rèn)為是萬能的。有經(jīng)驗(yàn)的 Python 程序員知道:他們不知道的東西總是比已知的更多,因此根本不可能是萬能的。編程行為產(chǎn)生的問題多于答案。關(guān)于 Python 的唯一優(yōu)點(diǎn)(正如我們今天在本文中所見)是它確實(shí)回答了人們的問題。至于我本人,覺得您不必因?yàn)槲規(guī)椭私饬诉@些 Python 必須提供的內(nèi)容而酬謝我。用 Python 編程自有回報。我從愛好 Python 的同伴處獲得的幫助也是不計報酬的。
上一頁
參考資料
關(guān)于作者
 |

|
 |
Patrick O‘Brien 是一位 Python 程序員、顧問和培訓(xùn)人員。他創(chuàng)建了 PyCrust,并且還是 PythonCard 項目的開發(fā)人員。他最近帶領(lǐng) PyPerSyst 團(tuán)隊將 Prevayler 移植到了 Python,并且繼續(xù)引領(lǐng)著這個項目向新的令人感興趣的領(lǐng)域前進(jìn)。可以在 Orbtech 網(wǎng)站了解更多有關(guān)作者和他工作方面的情況,或者可以通過 pobrien@ 與他聯(lián)系。
|
| |