python 學習筆記 9 -- Python強大的自省簡析

1. 什麼是自省?

自省就是自我評價、自我反省、自我批評、自我調控和自我教育,是孔子提出的一種自我道德修養的方法。他說:“見賢思齊焉,見不賢而內自省也。”(《論語·里仁》)當然,我們今天不是想說黨員的批評與自我批評。表明意思上,自省(introspection)是一種自我檢查行爲。在計算機編程中,自省是指這種能力:檢查某些事物以確定它是什麼、它知道什麼以及它能做什麼。自省向程序員提供了極大的靈活性和控制力。
本文介紹了 Python 編程語言的自省能力。整個 Python 語言對自省提供了深入而廣泛的支持。實際上,很難想象假如 Python 語言沒有其自省特性是什麼樣子。


2. Python的help

在進入python的終端IDE時我們就能見到help的身影:
zhouyl@zhouyl:~/repository/blog/python$ python
Python 2.7.3 (default, Jan  2 2013, 16:53:07) 
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.	

最後一行清晰的提釋我們可以輸入help來獲取更多的信息。

>>> help		# 輸入help之後,系統提示輸入help()進入交互式的help界面,或者使用help(obj)來獲取obj的help信息
Type help() for interactive help, or help(object) for help about object.
>>> help()		# 輸入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> 			# 在輸入一段“說明”後,進入交互式help界面(Ctrl+C或者輸入quit退出)
help> list		# 輸入list之後進入另一類似於“man”的窗口,完整介紹list


help> keywords	# 輸入keywords只打印python的關鍵字列表


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 


help> if		# 輸入關鍵字if,也是類似於上面list在另一窗口下介紹if

當然,我們也可以在python的IDE下使用help(object)來獲取某一對象的help信息,比如:
>>> help(os)		# 想看os模塊?^_^,沒門,你還沒加載呢
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'os' is not defined
>>> import os
>>> help(os)		# 加載之後即可查看os的help信息了


>>> 




3. sys 模塊

對於sys模塊,使用比較頻繁見的也比較多,那麼它是幹嗎的?python官網是這麼介紹的:“System-specific parameters and functions”(系統特定的參數和函數)。所以sys 模塊是提供關於 Python 本身的詳盡內在信息的模塊。通過導入模塊,並用點(.)符號引用其內容(如變量、函數和類)來使用模塊。 sys 模塊包含各種變量和函數,它們揭示了當前的 Python 解釋器有趣的詳細信息。


platform 變量告訴我們現在處於什麼操作系統上: sys.platform 屬性
>>> import sys
>>> sys.platform
'linux2'


在當前的 Python 中,版本以字符串和元組(元組包含對象序列)來表示:
>>> sys.version
'2.7.3 (default, Jan  2 2013, 16:53:07) \n[GCC 4.7.2]'
>>> sys.version_info
sys.version_info(major=2, minor=7, micro=3, releaselevel='final', serial=0)

maxint 變量反映了可用的最大整數值: sys.maxint 屬性
>>> sys.maxint
2147483647

argv 變量是一個包含命令行參數的列表(如果參數被指定的話)。第一項 argv[0] 是所運行腳本的路徑。當我們以交互方式運行 Python 時,這個值是空字符串: sys.argv 屬性

>>> sys.argv
['']

關於sys.argv的使用,我們在前面介紹函數的學習筆記中已有介紹,它可以用來接收函數的腳本,比如:
#!/usr/bin/python
import sys
print"The script name is "+sys.argv[0]
print"The first parameter is %s" % sys.argv[1]
print"The second parameter is %r" % sys.argv[2]

在腳本使用時,我們可以給腳本帶入參數:
long@zhouyl:/tmp/test$ python arg.py 1 2 3   # 雖然程序中只要求打印前2個參數,但是我們也可以帶更多的,只是沒打印而已
The script name is arg.py
The first parameter is 1
The second parameter is '2'


path 變量是模塊搜索路徑,Python 在導入期間將在其中的目錄列表中尋找模塊。:sys.path 屬性
>>> 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 屬性
>>> 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'>}



4.<dive into python>中的自省模塊

在閱讀dive into python時,第四章講到了自省,代碼如下:
# apihelper.py
def info(object, spacing=10, collapse=1):
    """Print methods and doc strings.
    
    Takes module, class, list, dictionary, or string."""
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])

使用起來也很方便,如下:
>>> from apihelper import info
>>> li = []
>>> info(li)
__add__    x.__add__(y) <==> x+y
__class__  list() -> new empty list list(iterable) -> new list initialized from iterable's items
...
>>> import apihelper
>>> info(apihelper)
info       Print methods and doc strings. Takes module, class, list, dictionary, or string.
>>> info(apihelper,20)
info                 Print methods and doc strings. Takes module, class, list, dictionary, or string.
>>> info(apihelper,20,0)
info                 Print methods and doc strings.
	
	Takes module, class, list, dictionary, or string.



4.1. 幾個內置函數介紹

其中使用了幾個函數,主要介紹如下:
dir([obj])
dir() 函數可能是 Python 自省機制中最著名的部分了。它返回傳遞給它的任何對象的屬性名稱經過排序的列表(會有一些特殊的屬性不包含在內)。如果不指定對象,則 dir() 返回當前作用域中的名稱(obj的默認值是當前的模塊對象)。

eg.

>>> li
[1, 4, 8, 16, 2, 3]
>>> dir(li)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']



getattr(obj, attr):
getattr是一個有用到無法置信的內置函數(自省的核心哦),可以返回任意對象的任何屬性。調用這個方法將返回obj中名爲attr值的屬性的值,例如如果attr爲'bar',則返回obj.bar。使用getattr函數,可以得到一個直到運行時才知道名稱的函數的引用。getattr的返回值是方法,可以調用。

eg.

	>>> li
	[1, 4, 8, 16]
	>>> li.append(2)
	>>> li
	[1, 4, 8, 16, 2]
	>>> getattr(li,'append')(3)
	>>> li
	[1, 4, 8, 16, 2, 3]


類似還有:
hasattr(obj, attr)
這個方法用於檢查obj是否有一個名爲attr的值的屬性,返回一個布爾值。
setattr(obj, attr, val):
調用這個方法將給obj的名爲attr的值的屬性賦值爲val。例如如果attr爲'bar',則相當於obj.bar = val。


callable(obj):
可以調用表示潛在行爲(函數和方法)的對象。可以用 callable() 函數測試參數對象的可調用性,參數對象可調用返回True,否則返回False。

eg.

>>> li
[1, 2 , 4, 8, 16]
>>> callable(li)
False
>>> callable(li.append)
True



string.ljust(length):
ljust用空格填充字符串以符合指定的長度。如果指定的長度(length)小於字符串的長度(len(string)),ljust將簡單滴返回未變化的字符串,不會截斷字符串。

eg.

	>>> s = "Zhou"
	>>> print s.ljust(10)+"Hello"
	Zhou      Hello
	>>> print s.ljust(1)+"Hello"
	ZhouHello



4.2. 列表解析

Python有着強大的列表解析即將列表映射到其他列表的強大能力。列表解析同過濾機制結合使用,使列表中有些元素被映射而跳過其他元素。
列表解析:
[mapping-expression for item in source-list]
eg.
>>> li = [1,2,4,8,16]
>>> [item*2 for item in li]
[2, 4, 8, 16, 32]
過濾列表語法:
[mapping-expression for item in source-list if filter-expression]
eg.
>>> li = [1,2,4,8,16]
>>> [item*2 for item in li if item%2 == 0]
[4, 8, 16, 32]
如上所示,過濾列表就是列表解析的擴展,前三部分是相同的,最後一步分添加if開頭的過濾表達式(過濾表達式可以是返回值爲真或假的任何表達式,而在Python中幾乎是任何東西)。


注意:如果仔細查看上面的apihelper.py實例,可以發現作者將一行命令放在多行,這也可以?這裏可以告訴你的是“列表解析是可以一個表達式分割成多行的”,因爲整個表達式都在一個方括號中括起來了。

4.3 逐析模塊

對於上述模塊中還有一些用法在上面兩節中並沒有提及。比如and-or用法和lambda函數,這些都在本系列的前文學習筆記中有所涉及,本文再不贅述。


下面對於上述模塊逐句進行分析:
def info(object, spacing=10, collapse=1):
# 首先,我們可以看到,定義的函數使用了三個參數:
@ object -- 當然是我們想要查看的目標了,本函數使用dir獲取object的屬性,而又因dir函數的強大,所以此處的object可以是python中的任意東西,包括模塊對象、函數對象、字符串對象、列表對象、字典對象...
@ spacing = 10,這裏定義了一個關鍵參數,也就是說,如果調用info函數時沒有給spacing賦值,spacing將使用默認的10。那麼這個spacing是幹嗎的?向下看,對!沒錯它是給ljust做參數的!也就是說長度不足10的字符串會使用空格補氣,而超過10的不做修改
@ collapse = 1,這裏也定了一個關鍵參數,如果不賦值,使用默認值1。collapse在函數中用作and-or用法的判斷語句。
    """Print methods and doc strings.


    Takes module, class, list, dictionary, or string."""

# 這是什麼?沒錯,當然是docstring了
    methodList = [method for method in dir(object) if callable(getattr(object, method))]
# 使用上面介紹的過濾列表,首先使用dir對object解析獲取object的所有屬性的一個列表,使用列表解析對其進行映射並通過if對其進行過濾,過濾條件是object.method可調用!因此,最後得到的methodList是一個object的所有可調用的屬性的列表
    processFunc = collapse and (lambda s: " ".join(s.split())) or (lambda s: s)
# 這一句定義了一個函數,對,只有一行的lambda函數哦,並使用第三個參數作爲and-or用法的判斷。如果collapse爲True,processFunc = lambda s: " ".join(s.split()),也即將s先使用split函數分割在使用" ".join將其使用空格連接起來。如果collapse爲False,processFunc = lambda s: s,也即對s不做任何處理
    print "\n".join(["%s %s" %
                      (method.ljust(spacing),
                       processFunc(str(getattr(object, method).__doc__)))
                     for method in methodList])
# 這裏就是上面提到的將列表解析放在幾行中使用的一個典型案例,首先使用method遍歷methodList列表(上面獲得的object的所有可調用的屬性的列表),在使用processFunc對object.method的__doc__(docstring)進行處理,與使用ljust調整好的method名一同打印出來。並使用"\n".join將每個不同的method信息之間進行分割,分割在不同行打印。




5. 打印一些函數的可獲取信息

下面是《Python 自省指南》中使用到的自省方法,主要使用了一些內置函數(id(),type(),repr()等)打印對象的信息:

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


我們把它添加到apihelper模塊中作爲我們的自省模塊的另一個方法,使用起來如下:

>>> import apihelper            # 加載我們的apihelper
>>> apihelper.info(apihelper)   # 調用前面的info函數查看apihelper中有哪些屬性
info       Print methods and doc strings. Takes module, class, list, dictionary, or string.
interrogate Print useful information about item.
>>> apihelper.interrogate(apihelper)    # 使用interrogate查看我們的apihelper的信息
NAME:     apihelper
CLASS:    module
ID:       3075144100
TYPE:     <type 'module'>
VALUE:    <module 'apihelper' from 'apihelper.py'>
CALLABLE: No
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "apihelper.py", line 30, in interrogate
    doc = doc.strip()   # Remove leading/trailing whitespace.
AttributeError: 'NoneType' object has no attribute 'strip'
>>> apihelper.interrogate(apihelper.info)   # 查看apihelper.info信息
NAME:     info
CLASS:    function
ID:       3075150932
TYPE:     <type 'function'>
VALUE:    <function info at 0xb74b1454>
CALLABLE: Yes
DOC:      Print methods and doc strings.
>>> apihelper.interrogate(123)              # 查看整數的信息
CLASS:    int
ID:       153002672
TYPE:     <type 'int'>
VALUE:    123
CALLABLE: No
DOC:      int(x[, base]) -> integer
>>> apihelper.interrogate("Hello")          # 查看字符串的信息
CLASS:    str
ID:       3075198208
TYPE:     <type 'str'>
VALUE:    'Hello'
CALLABLE: No
DOC:      str(object) -> string

至此,我們的自省模塊apihelper也就有了比較強大的功能,其自省能力也是不錯的~~~

=============================
引用:
[1] http://www.ibm.com/developerworks/cn/linux/l-pyint/index.html?ca=drs-#ibm-pcon
[2] dive into python - section4 :(在線可閱讀)
http://woodpecker.org.cn/diveintopython/power_of_introspection/index.html
[3] http://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章