作者:Vamei 出處:http://www.cnblogs.com/vamei?歡迎轉載,也請保留這段聲明。謝謝!
到現在為止,Python學習已經可以告一段落。下面的部分,我想討論Python的高級語法和底層實現。這一部分的內容并不是使用Python所必須的。但如果你想從事一些大型的Python開發(比如制作Python工具、寫一個框架等),你會希望對這一部分內容有所的了解。
一、特殊方法與多范式
Python?一切皆對象,但同時,Python還是一個多范式語言(multi-paradigm),你不僅可以使用面向對象的方式來編寫程序,還可以用面向過程的方式來編寫相同功能的程序(還有函數式、聲明式等,我們暫不深入)。Python的多范式依賴于Python對象中的特殊方法(special method)。
特殊方法名的前后各有兩個下劃線。特殊方法又被成為魔法方法(magic method),定義了許多Python?語法和表達方式,正如我們在下面的例子中將要看到的。當對象中定義了特殊方法的時候,Python也會對它們有“特殊優待”。比如定義了__init__()方法的類,會在創建對象的時候自動執行__init__()方法中的操作。
(可以通過dir()來查看對象所擁有的特殊方法,比如dir(1))。
1、運算符
Python的運算符是通過調用對象的特殊方法實現的。比如:
'abc' + 'xyz' # 連接字符串
實際執行了如下操作:
'abc'.__add__('xyz')
所以,在Python中,兩個對象是否能進行加法運算,首先就要看相應的對象是否有__add__()方法。一旦相應的對象有__add__()方法,即使這個對象從數學上不可加,我們都可以用加法的形式,來表達obj.__add__()所定義的操作。在Python中,運算符起到簡化書寫的功能,但它依靠特殊方法實現。
Python不強制用戶使用面向對象的編程方法。用戶可以選擇自己喜歡的使用方式(比如選擇使用+符號,還是使用更加面向對象的__add__()方法)。特殊方法寫起來總是要更費事一點。
2、內置函數
與運算符類似,許多內置函數也都是調用對象的特殊方法。比如:
len([1,2,3]) # 返回表中元素的總數
實際上做的是:
[1,2,3].__len__()
相對與__len__(),內置函數len()也起到了簡化書寫的作用。
3、表(list)元素引用
下面是我們常見的表元素引用方式:
li =[1, 2, 3, 4, 5, 6]
print(li[3])
上面的程序運行到li[3]的時候,Python發現并理解[]符號,然后調用__getitem__()方法。
li =[1, 2, 3, 4, 5, 6]
print(li.__getitem__(3))
4、函數
我們已經說過,在Python中,函數也是一種對象。實際上,任何一個有__call__()特殊方法的對象都被當作是函數。比如下面的例子:
class SampleMore(object):
def __call__(self, a):
return a + 5
add = SampleMore() # A function object
print(add(2)) # Call function
map(add, [2, 4, 5]) # Pass around function object
add為SampleMore類的一個對象,當被調用時,add執行加5的操作。add還可以作為函數對象,被傳遞給map()函數。
當然,我們還可以使用更“優美”的方式,想想是什么。
二、上下文管理器
上下文管理器(context manager)是Python2.5開始支持的一種語法,用于規定某個對象的使用范圍。一旦進入或者離開該使用范圍,會有特殊操作被調用 (比如為對象分配或者釋放內存)。它的語法形式是with...as...
1、關閉文件
我們會進行這樣的操作:打開文件,讀寫,關閉文件。程序員經常會忘記關閉文件。上下文管理器可以在不需要文件的時候,自動關閉文件。
下面我們看一下兩段程序:
# without context manager
f = open("new.txt", "w")
print(f.closed) # whether the file is open
f.write("Hello World!")
f.close()
print(f.closed)
以及:
# with context manager
with open("new.txt", "w") as f:
print(f.closed)
f.write("Hello World!")
print(f.closed)
兩段程序實際上執行的是相同的操作。我們的第二段程序就使用了上下文管理器 (with...as...)。上下文管理器有隸屬于它的程序塊。當隸屬的程序塊執行結束的時候(也就是不再縮進),上下文管理器自動關閉了文件 (我們通過f.closed來查詢文件是否關閉)。我們相當于使用縮進規定了文件對象f的使用范圍。
上面的上下文管理器基于f對象的__exit__()特殊方法(還記得我們如何利用特殊方法來實現各種語法?參看特殊方法與多范式)。當我們使用上下文管理器的語法時,我們實際上要求Python在進入程序塊之前調用對象的__enter__()方法,在結束程序塊的時候調用__exit__()方法。對于文件對象f來說,它定義了__enter__()和__exit__()方法(可以通過dir(f)看到)。在f的__exit__()方法中,有self.close()語句。所以在使用上下文管理器時,我們就不用明文關閉f文件了。
2、自定義
任何定義了__enter__()和__exit__()方法的對象都可以用于上下文管理器。文件對象f是內置對象,所以f自動帶有這兩個特殊方法,不需要自定義。
下面,我們自定義用于上下文管理器的對象,就是下面的myvow:
# customized object
class VOW(object):
def __init__(self, text):
self.text = text
def __enter__(self):
self.text = "I say: " + self.text # add prefix
return self # note: return an object
def __exit__(self,exc_type,exc_value,traceback):
self.text = self.text + "!" # add suffix
with VOW("I'm fine") as myvow:
print(myvow.text)
print(myvow.text)
我們的運行結果如下:
I say: I'm fine
I say: I'm fine!
我們可以看到,在進入上下文和離開上下文時,對象的text屬性發生了改變(最初的text屬性是"I'm fine")。
__enter__()返回一個對象。上下文管理器會使用這一對象作為as所指的變量,也就是myvow。在__enter__()中,我們為myvow.text增加了前綴 ("I say: ")。在__exit__()中,我們為myvow.text增加了后綴("!")。
注意: __exit__()中有四個參數。當程序塊中出現異常(exception),__exit__()的參數中exc_type, exc_value, traceback用于描述異常。我們可以根據這三個參數進行相應的處理。如果正常運行結束,這三個參數都是None。在我們的程序中,我們并沒有用到這一特性。
由于上下文管理器帶來的便利,它是一個值得使用的工具。
三、對象的屬性
Python一切皆對象(object),每個對象都可能有多個屬性(attribute)。Python的屬性有一套統一的管理方案。
1、屬性的__dict__系統
對象的屬性可能來自于其類定義,叫做類屬性(class attribute)。類屬性可能來自類定義自身,也可能根據類定義繼承來的。一個對象的屬性還可能是該對象實例定義的,叫做對象屬性(object attribute)。
對象的屬性儲存在對象的__dict__屬性中。__dict__為一個詞典,鍵為屬性名,對應的值為屬性本身。我們看下面的類和對象。chicken類繼承自bird類,而summer為chicken類的一個對象。
class bird(object):
feather = True
class chicken(bird):
fly = False
def __init__(self, age):
self.age = age
summer = chicken(2)
print(bird.__dict__)
print(chicken.__dict__)
print(summer.__dict__)
下面為我們的輸出結果:
{'__dict__': , '__module__': '__main__', '__weakref__': , 'feather': True, '__doc__': None}
{'fly': False, '__module__': '__main__', '__doc__': None, '__init__': }
{'age': 2}
第一行為bird類的屬性,比如feather。第二行為chicken類的屬性,比如fly和__init__方法。第三行為summer對象的屬性,也就是age。有一些屬性,比如__doc__,并不是由我們定義的,而是由Python自動生成。此外,bird類也有父類,是object類(正如我們的bird定義,class bird(object))。這個object類是Python中所有類的父類。
可以看到,Python中的屬性是分層定義的,比如這里分為object/bird/chicken/summer這四層。當我們需要調用某個屬性的時候,Python會一層層向上遍歷,直到找到那個屬性。(某個屬性可能出現再不同的層被重復定義,Python向上的過程中,會選取先遇到的那一個,也就是比較低層的屬性定義)。
當我們有一個summer對象的時候,分別查詢summer對象、chicken類、bird類以及object類的屬性,就可以知道summer對象所有的__dict__,就可以找到通過對象summer可以調用和修改的所有屬性了。下面兩種屬性修改方法等效:
summer.__dict__['age'] = 3
print(summer.__dict__['age'])
summer.age = 5
print(summer.age)
(上面的情況中,我們已經知道了summer對象的類為chicken,而chicken類的父類為bird。如果只有一個對象,而不知道它的類以及其他信息的時候,我們可以利用__class__屬性找到對象的類,然后調用類的__base__屬性來查詢父類) 。
2、特性
同一個對象的不同屬性之間可能存在依賴關系。當某個屬性被修改時,我們希望依賴于該屬性的其他屬性也同時變化。這時,我們不能通過__dict__的方式來靜態的儲存屬性。Python提供了多種即時生成屬性的方法。其中一種稱為特性(property)。特性是特殊的屬性。比如我們為chicken類增加一個特性adult。當對象的age超過1時,adult為True;否則為False:
class bird(object):
feather = True
class chicken(bird):
fly = False
def __init__(self, age):
self.age = age
def getAdult(self):
if self.age > 1.0: return True
else: return False
adult = property(getAdult) # property is built-in
summer = chicken(2)
print(summer.adult)
summer.age = 0.5
print(summer.adult)
特性使用內置函數property()來創建。property()最多可以加載四個參數。前三個參數為函數,分別用于處理查詢特性、修改特性、刪除特性。最后一個參數為特性的文檔,可以為一個字符串,起說明作用。
我們使用下面一個例子進一步說明:
class num(object):
def __init__(self, value):
self.value = value
def getNeg(self):
return -self.value
def setNeg(self, value):
self.value = -value
def delNeg(self):
print("value also deleted")
del self.value
neg = property(getNeg, setNeg, delNeg, "I'm negative")
x = num(1.1)
print(x.neg)
x.neg = -22
print(x.value)
print(num.neg.__doc__)
del x.neg
上面的num為一個數字,而neg為一個特性,用來表示數字的負數。當一個數字確定的時候,它的負數總是確定的;而當我們修改一個數的負數時,它本身的值也應該變化。這兩點由getNeg和setNeg來實現。而delNeg表示的是,如果刪除特性neg,那么應該執行的操作是刪除屬性value。property()的最后一個參數("I'm negative")為特性negative的說明文檔。
3、使用特殊方法__getattr__
我們可以用__getattr__(self, name)來查詢即時生成的屬性。當我們查詢一個屬性時,如果通過__dict__方法無法找到該屬性,那么Python會調用對象的__getattr__方法,來即時生成該屬性。比如:
class bird(object):
feather = True
class chicken(bird):
fly = False
def __init__(self, age):
self.age = age
def __getattr__(self, name):
if name == 'adult':
if self.age > 1.0: return True
else: return False
else: raise AttributeError(name)
summer = chicken(2)
print(summer.adult)
summer.age = 0.5
print(summer.adult)
print(summer.male)
每個特性需要有自己的處理函數,而__getattr__可以將所有的即時生成屬性放在同一個函數中處理。__getattr__可以根據函數名區別處理不同的屬性。比如上面我們查詢屬性名male的時候,raise AttributeError。
(Python中還有一個__getattribute__特殊方法,用于查詢任意屬性。__getattr__只能用來查詢不在__dict__系統中的屬性)
__setattr__(self, name, value)和__delattr__(self, name)可用于修改和刪除屬性。它們的應用面更廣,可用于任意屬性。
4、即時生成屬性的其他方式
即時生成屬性還可以使用其他的方式,比如descriptor ( descriptor類實際上是property()函數的底層,property()實際上創建了一個該類的對象 ) 。有興趣可以進一步查閱。
作業
嘗試下面的操作,看看效果,再想想它的對應運算符:
(1.8).__mul__(2.0)
True.__or__(False)
嘗試下面的操作,想一下它的對應內置函數:
(-1).__abs__()
(2.3).__int__()
嘗試看下面的操作,想想它的對應:
li.__setitem__(3, 0)
{'a':1, 'b':2}.__delitem__('a')