十一:函數和函數式編程
11.1 什么是函數?
函數是對程序邏輯進行結構化或過程化的一種編程方法。能將整塊代碼巧妙地隔離成易于管理
的小塊,把重復代碼放到函數中而不是進行大量的拷貝--這樣既能節省空間,也有助于保持一致性,因為你只需改變單個的拷貝而無須去尋找再修改大量復制代碼的拷貝
11.2 調用函數
11.2.1.函數操作符
11.2.2關鍵字參數
調用者通過函數調用中的參數名字來區
分參數。這樣規范允許參數缺失或者不按順序,因為解釋器能通過給出的關鍵字來匹配參數的值。
def foo(x):
foo_suite # presumably does some processing with 'x'
標準調用 foo():foo(42) foo('bar') foo(y)
關鍵字調用 foo():foo(x=42)foo(x='bar') foo(x=y)
11.2.3.默認參數
默認參數就是聲明了默認值的參數。因為給參數賦予了默認值,所以, 在函數調用時,不向該
參數傳入值也是允許的
11.3.4.函數屬性
11.3.6 *函數(與方法)裝飾器
from time import ctime, sleep
def tsfunc(func):
def wrappedFunc():
print '[%s] %s() called' % (
ctime(), func.__name__)
return func()
return wrappedFunc
def demo():
print 'demo'
# 實際上裝飾器調用類似于下面的調用
foo = tsfunc(demo)
@tsfunc
def foo():
pass
#使用帶裝飾器的方法
foo()
sleep(4)
for i in range(2):
sleep(1)
foo()
11.7 函數式編程
內建函數 apply()、filter()、map()、reduce()
內建函數 描述
apply(func[, nkw][, kw]) a 用可選的參數來調用 func,nkw 為非關鍵字參數,kw 關
鍵字參數;返回值是函數調用的返回值。
filter(func, seq)b 調用一個布爾函數 func 來迭代遍歷每個 seq 中的元素; 返回一個
使 func 返回值為 ture 的元素的序列。
map(func, seq1[,seq2...])b 將函數 func 作用于給定序列(s)的每個元素,并用一個列表來提
供返回值;如果 func 為 None, func 表現為一個身份函數,返回
一個含有每個序列中元素集合的 n 個元組的列表。
reduce(func, seq[, init]) 將二元函數作用于 seq 序列的元素,每次攜帶一對(先前的結果
以及下一個序列元素),連續的將現有的結果和下雨給值作用在獲
得的隨后的結果上,最后減少我們的序列為一個單一的返回值;如
果初始值 init 給定,第一個比較會是 init 和第一個序列元素而不
是序列的頭兩個元素。
使用舉例:
add = lambda x,y : x + y
print add(1,2)
sequence = [1,2,3,4,5,6,7,8,9,10]
fun = lambda x : x % 2 == 0
seq = filter(fun,sequence)
print seq
def filter(fun,seq):
filter_seq = []
for item in seq:
if fun(item):
filter_seq.append(item)
return filter_seq
11.8 變量作用域
11.8.1 全局變量與局部變量
核心筆記:搜索標識符(aka 變量,名字,等等)
當搜索一個標識符的時候,python 先從局部作用域開始搜索。如果在局部作用域內沒有找到那
個名字,那么就一定會在全局域找到這個變量否則就會被拋出 NameError 異常。
一個變量的作用域和它寄住的名字空間相關。我們會在 12 章正式介紹名字空間;對于現在只能
說子空間僅僅是將名字映射到對象的命名領域,現在使用的變量名字虛擬集合。作用域的概念和用
于找到變量的名字空間搜索順序相關。當一個函數執行的時候,所有在局部命名空間的名字都在局
部作用域內。那就是當查找一個變量的時候,第一個被搜索的名字空間。如果沒有在那找到變量的
話,那么就可能找到同名的全局變量。這些變量存儲(搜索)在一個全局以及內建的名字空間,。
僅僅通過創建一個局部變量來“ 隱藏“或者覆蓋一個全局變量是有可能的。回想一下,局部名
字空間是首先被搜索的,存在于其局部作用域。如果找到一個名字,搜索就不會繼續去尋找一個全
局域的變量,所以在全局或者內建的名字空間內,可以覆蓋任何匹配的名字
11.8.4 閉包
如果在一個內部函數里,對在外部作用域(但不是在全局作用域)的
變量進行引用,那么內部函數就被認為是 closure。定義在外部函數內的但由內部函數引用或者使用
的變量被稱為自由變量
11.8.6 變量作用域和名字空間
11.10 生成器
協程是可以運行的獨立函數調
用,可以暫停或者掛起,并從程序離開的地方繼續或者重新開始。在有調用者和(被調用的)協同
程序也有通信。舉例來說,當協同程序暫停的時候,我們能從其中獲得一個中間的返回值,當調用
回到程序中時,能夠傳入額外或者改變了的參數,但仍能夠從我們上次離開的地方繼續,并且所有
狀態完整。掛起返回出中間值并多次繼續的協同程序被稱為生成器,那就是 python 的生成器真正在
做的事。
==什么是python 式的生成器?==
從句法上講,生成器是一個帶 yield 語句的函數。一個函數或者子
程序只返回一次,但一個生成器能暫停執行并返回一個中間的結果----那就是 yield 語句的功能, 返
回一個值給調用者并暫停執行。當生成器的 next()方法被調用的時候,它會準確地從離開地方繼續
(當它返回[一個值以及]控制給調用者時)
==何時使用生成器==
使用生成器最好的地方就是當你正迭代穿越一個巨大的數據集合,而重復迭代這個數據集合是
一個很麻煩的事,比如一個巨大的磁盤文件,或者一個復雜的數據庫查詢。對于每行的數據,你希
望執行非元素的操作以及處理,但當正指向和迭代過它的時候,你“不想失去你的地盤“。
11.10.2 加強的生成器特性
十二:模塊
12.1 什么是模塊
一個文件被看作是一個獨立模塊, 一個模塊也可以被看作是一個文件,模塊的文件名就是模
塊的名字加上擴展名 .py 。與其它可以導入類
(class)的語言不同,在 Python 中你導入的是模塊或模塊屬性
12.3名稱空間
在執行期間有兩個或三個活動的名稱空間。 這==三個名稱空間分別是
局部名稱空間, 全局名稱空間和內建名稱空間,== 但局部名稱空間在執行期間是不斷變化的, 所以我
們說"兩個或三個"。 從名稱空間中訪問這些名字依賴于它們的加載順序, 或是系統加載這些名稱空間的順序。
==Python 解釋器首先加載內建名稱空間。 它由 builtins 模塊中的名字構成。 隨后加載執
行模塊的全局名稱空間, 它會在模塊開始執行后變為活動名稱空間。 這樣我們就有了兩個活動的名稱空間==
如果在執行期間調用了一個函數, 那么將創建出第三個名稱空間, 即局部名稱空間。 我們可以
通過 globals() 和 locals() 內建函數判斷出某一名字屬于哪個名稱空
核心筆記: builtins 和 builtin
builtins 模塊和 builtin 模塊不能混淆。 雖然它們的名字相似——尤其對于新手來
說。 builtins 模塊包含內建名稱空間中內建名字的集合。 其中大多數(如果不是全部的話)來
自 builtin 模塊, 該模塊包含內建函數, 異常以及其他屬性。 在標準 Python 執行環境下,
builtins 包含 builtin 的所有名字。 Python 曾經有一個限制執行模式, 允許你修改
builtins , 只保留來自 builtin 的一部分, 創建一個沙盒(sandbox)環境。但是, 因為
它有一定的安全缺陷, 而且修復它很困難, Python 已經不再支持限制執行模式。
如果在執行期間調用了一個函數, 那么將創建出第三個名稱空間, 即局部名稱空間。 我們可以
通過 globals() 和 locals() 內建函數判斷出某一名字屬于哪個名稱空間。
12.3.1 名稱空間與變量作用域比較
名稱空間是純粹意義上的名字和對象間的映射關系, 而作用域還指出了從用戶代碼的哪些物
理位置可以訪問到這些名字
== 那么確定作用域的規則是如何聯系到名稱空間的呢? 它所要做的就是名稱查詢. 訪問一個屬性
時, 解釋器必須在三個名稱空間中的一個找到它。 首先從局部名稱空間開始, 如果沒有找到, 解釋
器將繼續查找全局名稱空間 ==
class MyUltimatePythonStorageDevice(object):
pass
bag = MyUltimatePythonStorageDevice()
bag.x = 100 #動態加載屬性
bag.y = 200
bag.version = 0.1
bag.completed = False
你可以把任何想要的東西放入一個名稱空間里。 像這樣使用一個類(實例)是很好的, 你甚至
不需要知道一些關于 OOP 的知識(注解: 類似這樣的變量叫做實例屬性。) 不管名字如何, 這個實
例只是被用做一個名稱空間
12.4 導入模塊
12.5 模塊導入的特性
12.5.1 載入時執行模塊
==加載模塊會導致這個模塊被"執行"。 也就是被導入模塊的頂層代碼將直接被執行。 這通常包
括設定全局變量以及類和函數的聲明。 如果有檢查 name 的操作, 那么它也會被執行。==
當然, 這樣的執行可能不是我們想要的結果。 你應該把盡可能多的代碼封裝到函數。 明確地說,
只把函數和模塊定義放入模塊的頂層是良好的模塊編程習慣
12.5.2 導入(import )和加載(load)
==一個模塊只被加載一次, 無論它被導入多少次。 這可以阻止多重導入時代碼被多次執行。 例
如你的模塊導入了 sys 模塊, 而你要導入的其他 5 個模塊也導入了它, 那么每次都加載 sys (或
是其他模塊)不是明智之舉! 所以, 加載只在第一次導入時發生。==
12.5.3 導入到當前名稱空間的名稱
12.5.3 導入到當前名稱空間的名稱
12.6 模塊內建函數
12.6.1 import()
這意味著 import 語句調用 import() 函數完成它的工作。提供這個函數是為了讓有特殊需要的用戶覆蓋它, 實現
自定義的導入算法
12.6.2 globals() 和 locals()
globals() 和 locals() 內建函數分別返回調用者全局和局部名稱空間的字典。 在一個函數內
部, 局部名稱空間代表在函數執行時候定義的所有名字, locals() 函數返回的就是包含這些名字
的字典。 globals() 會返回函數可訪問的全局名字。
在全局名稱空間下, globals() 和 locals() 返回相同的字典, 因為這時的局部名稱空間就是
全局空間。
12.7 包
包是一個有層次的文件目錄結構, 它定義了一個由模塊和子包組成的 Python 應用程序執行
環境。Python 1.5 加入了包, 用來幫助解決如下問題:
- 為平坦的名稱空間加入有層次的組織結構
- 允許程序員把有聯系的模塊組合到一起
- 允許分發者使用目錄結構而不是一大堆混亂的文件
- 幫助解決有沖突的模塊名稱
與類和模塊相同, 包也使用句點屬性標識來訪問他們的元素。 使用標準的 import 和
from-import 語句導入包中的模塊。
12.7.1 目錄結構
_init_.py 文件。 這些是初始化模塊,
from-import 語句導入子包時需要用到它。 如果沒有用到, 他們可以是空文件。
12.7.2 使用 from-import 導入包
包同樣支持 from-import all 語句:
from package.module import *
這樣的語句會導入哪些文件取決于操作系統的文件系統. 所以我們在init.py 中加入 all 變量. 該變量包含執行這樣的語句時應該導入的模塊的名字. 它由一個模塊名字符串列表組成
2.7.3 絕對導入
包的使用越來越廣泛, 很多情況下導入子包會導致和真正的標準庫模塊發生(事實上是它們的
名字)沖突。 包模塊會把名字相同的標準庫模塊隱藏掉, 因為它首先在包內執行相對導入, 隱藏掉
標準庫模塊。
為 此 ,==所有的導入現在都被認為是絕對的,也就是說這些名字必須通過 Python路徑(sys.path 或是 PYTHONPATH )來訪問==
12.7.4 相對導入
絕對導入特性限制了模塊作者的一些特權。失去了 import 語句的自由, 必須有新
的特性來滿足程序員的需求。這時候, 我們有了相對導入。 相對導入特性稍微地改變了 import 語
法, 讓程序員告訴導入者在子包的哪里查找某個模塊。==因為 import 語句總是絕對導入的, 所以相
對導入只應用于 from-import 語句。==
12.8 模塊的其他特性
12.8.1 自動載入的模塊
==當 Python 解釋器在標準模式下啟動時, 一些模塊會被解釋器自動導入, 用于系統相關操作。
唯一一個影響你的是 _builtin_ 模塊, 它會正常地被載入, 這和 _builtins_ 模塊相同.==
sys.modules 變量包含一個由當前載入(完整且成功導入)到解釋器的模塊組成的字典, 模塊名作為鍵, 它們的位置作為值。
12.8.2 阻止屬性導入
如果你不想讓某個模塊屬性被 "from module import *" 導入 , 那么你可以給你不想導入的屬
性名稱加上一個下劃線( _ )。 不過如果你導入了整個模塊或是你顯式地導入某個屬性(例如 import
foo._bar ), 這個隱藏數據的方法就不起作用了
十四:執行環境
14.1.4 類的實例
python 給類提供了名為call的特別方法,該方法允許程序員創建可調用的對象(實例)。默
認情況下,call()方法是沒有實現的,這意味著大多數實例都是不可調用的。然而,如果在類
定義中覆蓋了這個方法,那么這個類的實例就成為可調用的了。調用這樣的實例對象等同于調用
call()方法
14.2 代碼對象
可調用的對象是 python 執行環境里最重要的部分, 然而他們只是冰山一角。python 語句, 賦值,
表達式,甚至還有模塊構成了更宏大的場面。這些可執行對象無法像可調用物那樣被調用。更確切
地說,這些對象只是構成可執行代碼塊的拼圖的很小一部分,而這些代碼塊被稱為==代碼對象==
14.3 可執行的對象聲明和內建函數
Python 提供了大量的 BIF 來支持可調用/可執行對象,其中包括 exec 語句。這些函數幫助程序
員執行代碼對象,也可以用內建函數 complie()來生成代碼對象。
14.3.1 callable()
callable()是一個布爾函數,確定一個對象是否可以通過函數操作符(())來調用。如果函數可
調用便返回 True,否則便是 False
14.3.2 compile()
compile()函數允許程序員在運行時刻迅速生成代碼對象,然后就可以用 exec 語句或者內建函
數 eval()來執行這些對象或者對它們進行求值。一個很重要的觀點是:==exec 和 eval()都可以執行字符串格式的Python代碼==。當執行字符串形式的代碼時,每次都必須對這些代碼進行字節編譯處理。compile()函數提供了一次性字節代碼預編譯,以后每次調用的時候,都不用編譯了。
compile 的三個參數都是必需的,第一參數代表了要編譯的 python 代碼。第二個字符串,雖然
是必需的,但通常被置為空串。該參數代表了存放代碼對象的文件的名字(字符串類型)。compile 的
通常用法是動態生成字符串形式的 Python 代碼, 然后生成一個代碼對象——代碼顯然沒有存放在
任何文件。
最后的參數是個字符串,它用來表明代碼對象的類型。有三個可能值:
- 'eval' 可求值的表達式[和 eval()一起使用]
- 'single' 單一可執行語句[和 exec 一起使用]
- 'exec' 可執行語句組[和 exec 一起使用]
可求值表達式
>>> eval_code = compile('100 + 200', '', 'eval')
>>> eval(eval_code)
300
單一可執行語句
>>> single_code = compile('print "Hello world!"', '', 'single')
>>> single_code
<code object ? at 120998, file "", line 0>
>>> exec single_code
Hello world!
可執行語句組
>>> exec_code = compile("""
... req = input('Count how many numbers? ')
... for eachNum in range(req):
... print eachNum
... """, '', 'exec')
>>> exec exec_code
Count how many numbers? 6
0
1
2
3
4
5
14.3.3 eval()
14.4 執行其他(Python)程序
當討論執行其他程序時,我們把它們分類為 python 程序和其他所有的非 python 程序,后者包
括了二進制可執行文件或其他腳本語言的源代碼。我們先討論如何運行其他的 python 程序,然后是如何用 os 模塊調用外部程序。
14.4.1 導入
核心筆記:當模塊導入后,就執行所有的模塊
這只是一個善意的提醒:在先前的第 3 章和第 12 章已經談過了,現在再說一次,當導入 python
模塊后,就執行所有的模塊!當導入 python 模塊后,會執行該模塊!當你導入 foo 模塊時候,它運行
所有最高級別的(即沒有縮進的)python 代碼,比如,'main()’。如果 foo 含有 bar 函數的聲明,
那么便執行 def foo(...)。 再問一次為什么會這樣做呢?…… 由于某些原因, bar 必須被識別為 foo
模塊中一個有效的名字,也就是說 bar 在 foo 的名字空間中,其次,解釋器要知道它是一個已聲明
的函數,就像本地模塊中的任何一個函數。現在我們知道要做什么了,那么如何處理那些不想每次
導入都執行的代碼呢?縮進它,并放入 if name == 'main' 的內部。