標籤:相同 聲明式 編程範式 gic 返回 round 需要 close 關閉
特殊方法與多範式
Python一切皆對象,但同時,Python還是一個多範式語言(multi-paradigm),你不僅可以使用物件導向的方式來編寫程式,還可以用面向過程的方式來編寫相同功能的程式(還有函數式、聲明式等,我們暫不深入)。Python的多範式依賴於Python對象中的特殊方法(special method)。
特殊方法名的前後各有兩個底線。特殊方法又被成為魔法方法(magic method),定義了許多Python文法和表達方式,正如我們在下面的例子中將要看到的。當對象中定義了特殊方法的時候,Python也會對它們有“特殊優待”。比如定義了__init__()方法的類,會在建立對象的時候自動執行__init__()方法中的操作。
對於內建的對象來說(比如整數、表、字串等),它們所需要的特殊方法都已經在Python中準備好了。而使用者自己定義的對象也可以通過增加特殊方法,來實現自訂的文法。特殊方法比較靠近Python的底層,許多Python功能的實現都要依賴於特殊方法。
上下文管理器 context manager ,是Python2.5之後開始支援一個中文法 用於規定某個對象的使用範圍。一旦進入或者離開該使用範圍,會有特殊操作被調用 (比如為對象分配或者釋放記憶體)。它的文法形式是with...as...
關閉檔案我們會進行這樣的操作:開啟檔案,讀寫,關閉檔案。程式員經常會忘記關閉檔案。上下文管理器可以在不需要檔案的時候,自動關閉檔案。下面我們看一下兩段程式:
# without context managerf = open("new.txt", "w")print(f.closed) # whether the file is openf.write("Hello World!")f.close()print(f.closed)
以及:
# with context managerwith 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檔案了。
自訂:任何定義了__enter__()和__exit__()方法的對象都可以用於上下文管理器。檔案對象f是內建對象,所以f自動帶有這兩個特殊方法,不需要自訂。
對象的屬性 Python一切皆對象(object),每個對象都可能有多個屬性(attribute)。Python的屬性有一套統一的管理方案。
對象的屬性可能來自於其類定義,叫做類屬性(class attribute)。類屬性可能來自類定義自身,也可能根據類定義繼承來的。一個對象的屬性還可能是該對象執行個體定義的,叫做對象屬性(object attribute)。
對象的屬性儲存在對象的__dict__屬性中。__dict__為一個詞典,鍵為屬性名稱,對應的值為屬性本身。
閉包 閉包(closure)是函數式編程的重要的文法結構。函數式編程是一種編程範式 (而面向過程編程和物件導向編程也都是編程範式)。在面向過程編程中,我們見到過函數(function);在物件導向編程中,我們見過對象(object)。函數和對象的根本目的是以某種邏輯方式組織代碼,並提高代碼的可重複使用性(reusability)。閉包也是一種組織代碼的結構,它同樣提高了代碼的可重複使用性。 函數是一個對象,可以作為某個函數的返回結果:
def line_conf(): def line(x): return 2*x+1 return line # return a function objectmy_line = line_conf()print(my_line(5))
上面的代碼可以成功運行。line_conf的返回結果被賦給line對象。上面的代碼將列印11。 如果line()的定義中引用了外部的變數,會發生什麼呢?
def line_conf(): b = 15 def line(x): return 2*x+b return line # return a function objectb = 5my_line = line_conf()print(my_line(5))
我們可以看到,line定義的隸屬程式塊中引用了高層級的變數b,但b資訊存在於line的定義之外 (b的定義並不在line的隸屬程式塊中)。我們稱b為line的環境變數。事實上,line作為line_conf的傳回值時,line中已經包括b的取值(儘管b並不隸屬於line)。
上面的代碼將列印25,也就是說,line所參照的b值是函數對象定義時可供參考的b值,而不是使用時的b值。 下面看一個閉包的實際例子:
def line_conf(a, b): def line(x): return ax + b return lineline1 = line_conf(1, 1)line2 = line_conf(4, 5)print(line1(5), line2(5))
這個例子中,函數line與環境變數a,b構成閉包。在建立閉包的時候,我們通過line_conf的參數a,b說明了這兩個環境變數的取值,這樣,我們就確定了函數的最終形式(y = x + 1和y = 4x + 5)。我們只需要變換參數a,b,就可以獲得不同的直線表達函數。由此,我們可以看到,閉包也具有提高代碼可複用性的作用。如果沒有閉包,我們需要每次建立直線函數的時候同時說明a,b,x。這樣,我們就需要更多的參數傳遞,也減少了代碼的可移植性。利用閉包,我們實際上建立了泛函。line函數定義一種廣泛意義的函數。這個函數的一些方面已經確定(必須是直線),但另一些方面(比如a和b參數待定)。隨後,我們根據line_conf傳遞來的參數,通過閉包的形式,將最終函數確定下來。
裝飾器 我們先定義兩個簡單的數學函數,一個用來計算平方和,一個用來計算平方差:
# get square sumdef square_sum(a, b): return a**2 + b**2# get square diffdef square_diff(a, b): return a**2 - b**2
print(square_sum(3, 4))
print(square_diff(3, 4))
在擁有了基本的數學功能之後,我們可能想為函數增加其它的功能,比如列印輸入。我們可以改寫函數來實現這一點:
# modify: print input# get square sumdef square_sum(a, b): print("intput:", a, b) return a**2 + b**2# get square diffdef square_diff(a, b): print("input", a, b) return a**2 - b**2print(square_sum(3, 4))print(square_diff(3, 4))
我們修改了函數的定義,為函數增加了功能。 現在,我們使用裝飾器來實現上述修改:
def decorator(F): def new_F(a, b): print("input", a, b) return F(a, b) return new_F# get square [email protected]def square_sum(a, b): return a**2 + b**2# get square [email protected]def square_diff(a, b): return a**2 - b**2print(square_sum(3, 4))print(square_diff(3, 4))
裝飾器可以用def的形式定義,如上面代碼中的decorator。裝飾器接收一個可調用對象作為輸入參數,並返回一個新的可調用對象。裝飾器建立了一個可調用對象,也就是上面的new_F。new_F中,我們增加了列印的功能,並通過調用F(a, b)來實現原有函數的功能。
定義好裝飾器後,我們就可以通過@文法使用了。在函數square_sum和square_diff定義之前調用@decorator,我們實際上將square_sum或square_diff傳遞給decorator,並將decorator返回的新的可調用對象賦給原來的函數名(square_sum或square_diff)。 所以,當我們調用square_sum(3, 4)的時候,就相當於:square_sum = decorator(square_sum)square_sum(3, 4)
我們知道,Python中的變數名和對象是分離的。變數名可以指向任意一個對象。從本質上,裝飾器起到的就是這樣一個重新指向變數名的作用(name binding),讓同一個變數名指向一個新返回的可調用對象,從而達到修改可調用對象的目的。
與加工函數類似,我們可以使用裝飾器加工類的方法。 如果我們有其他的類似函數,我們可以繼續調用decorator來修飾函數,而不用重複修改函數或者增加新的封裝。這樣,我們就提高了程式的可重複利用性,並增加了程式的可讀性。
總結總結
- 上下文管理器
- 在不需要檔案的時候,自動關閉檔案
- with...as...
- 對象屬性
- 閉包
- 函數line,和環境變數 a,b 構成了閉包,提高了代碼的複用性。
- 裝飾器
python基礎學習筆記3