Python學習第四天

一、裝飾器

函數調用順序:

其他高級語言類似,Python 不允許在函數未聲明之前,對其進行引用或者調用

高階函數:

滿足下列條件之一就可成函數為高階函數

  1. 某一函數當做參數傳入另一個函數中

  2. 函數的返回值包含n個函數,n>0

內嵌函數和變量作用域:

定義:在一個函數體內創建另外一個函數,這種函數就叫內嵌函數(基于python支持靜態嵌套域)

閉包:

如果在一個內部函數里,對在外部作用域(但不是在全局作用域)的變量進行引用,那么內部函數就被認為是 closure

內嵌函數+高階函數+閉包=》裝飾器

范例一:函數參數固定

 1 def decorartor(func):
 2     def wrapper(n):
 3         print 'starting'
 4         func(n)
 5         print 'stopping'
 6     return wrapper
 7  
 8  
 9 def test(n):
10     print 'in the test arg is %s' %n
11  
12 decorartor(test)('alex')
View Code

范例二:函數參數不固定

 1 def decorartor(func):
 2     def wrapper(*args,**kwargs):
 3         print 'starting'
 4         func(*args,**kwargs)
 5         print 'stopping'
 6     return wrapper
 7  
 8  
 9 def test(n,x=1):
10     print 'in the test arg is %s' %n
11  
12 decorartor(test)('alex',x=2)
View Code

范例三:無參裝飾器

 1 import time
 2 def decorator(func):
 3         def wrapper(*args,**kwargs):
 4             start=time.time()
 5             func(*args,**kwargs)
 6             stop=time.time()
 7             print 'run time is %s ' %(stop-start)
 8             print timeout
 9         return wrapper
10  
11 @decorator
12 def test(list_test):
13     for i in list_test:
14         time.sleep(0.1)
15         print '-'*20,i
16   
17  
18 #decorator(test)(range(10)) 
19 test(range(10))
View Code

范例四:有參裝飾器

 1 import time
 2 def timer(timeout=0):
 3     def decorator(func):
 4         def wrapper(*args,**kwargs):
 5             start=time.time()
 6             func(*args,**kwargs)
 7             stop=time.time()
 8             print 'run time is %s ' %(stop-start)
 9             print timeout
10         return wrapper
11     return decorator
12 @timer(2)
13 def test(list_test):
14     for i in list_test:
15         time.sleep(0.1)
16         print '-'*20,i
17   
18 #timer(timeout=10)(test)(range(10))
19 test(range(10))
View Code

二、生成器

1、列表生成式

1 >>> a = [i+1 for i in range(10)]
2 >>> a
3 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
View Code

2、生成器

通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。

所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。

要創建一個generator,有很多種方法。第一種方法很簡單,只要把一個列表生成式的[]改成(),就創建了一個generator:

1 >>> L = [x * x for x in range(10)]
2 >>> L
3 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
4 >>> g = (x * x for x in range(10))
5 >>> g
6 <generator object <genexpr> at 0x1022ef630>

斐波拉契數列用列表生成式寫不出來,但是,用函數把它打印出來卻很容易:

1 def fib(max):
2     n, a, b = 0, 0, 1
3     while n < max:
4         print(b)
5         a, b = b, a + b
6         n = n + 1
7     return 'done'

仔細觀察,可以看出,fib函數實際上是定義了斐波拉契數列的推算規則,可以從第一個元素開始,推算出后續任意的元素,這種邏輯其實非常類似generator。

也就是說,上面的函數和generator僅一步之遙。要把fib函數變成generator,只需要把print(b)改為yield b就可以了:

 1 def fib(max):
 2     n,a,b = 0,0,1
 3 
 4     while n < max:
 5         #print(b)
 6         yield  b
 7         a,b = b,a+b
 8 
 9         n += 1
10 
11     return 'done'

這就是定義generator的另一種方法。如果一個函數定義中包含yield關鍵字,那么這個函數就不再是一個普通函數,而是一個generator:

1 >>> f = fib(6)
2 >>> f
3 <generator object fib at 0x104feaaa0>

這里,最難理解的就是generator和函數的執行流程不一樣。函數是順序執行,遇到return語句或者最后一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。

 1 data = fib(10)
 2 print(data)
 3 
 4 print(data.__next__())
 5 print(data.__next__())
 6 print("干點別的事")
 7 print(data.__next__())
 8 print(data.__next__())
 9 print(data.__next__())
10 print(data.__next__())
11 print(data.__next__())
12 
13 #輸出
14 <generator object fib at 0x101be02b0>
15 1
16 干點別的事
17 3
18 8

但是用for循環調用generator時,發現拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIterationvalue中:

 1 >>> g = fib(6)
 2 >>> while True:
 3 ...     try:
 4 ...         x = next(g)
 5 ...         print('g:', x)
 6 ...     except StopIteration as e:
 7 ...         print('Generator return value:', e.value)
 8 ...         break
 9 ...
10 g: 1
11 g: 1
12 g: 2
13 g: 3
14 g: 5
15 g: 8
16 Generator return value: done

三、迭代器

我們已經知道,可以直接作用于for循環的數據類型有以下幾種:

一類是集合數據類型,如listtupledictsetstr等;

一類是generator,包括生成器和帶yield的generator function。

這些可以直接作用于for循環的對象統稱為可迭代對象:Iterable

可以使用isinstance()判斷一個對象是否是Iterable對象

 1 >>> from collections import Iterable
 2 >>> isinstance([], Iterable)
 3 True
 4 >>> isinstance({}, Iterable)
 5 True
 6 >>> isinstance('abc', Iterable)
 7 True
 8 >>> isinstance((x for x in range(10)), Iterable)
 9 True
10 >>> isinstance(100, Iterable)
11 False

*可以被next()函數調用并不斷返回下一個值的對象稱為迭代器:Iterator

 1 可以使用isinstance()判斷一個對象是否是Iterator對象:
 2 
 3 >>> from collections import Iterator
 4 >>> isinstance((x for x in range(10)), Iterator)
 5 True
 6 >>> isinstance([], Iterator)
 7 False
 8 >>> isinstance({}, Iterator)
 9 False
10 >>> isinstance('abc', Iterator)
11 False

生成器都是Iterator對象,但listdictstr雖然是Iterable,卻不是Iterator

listdictstrIterable變成Iterator可以使用iter()函數:

1 >>> isinstance(iter([]), Iterator)
2 True
3 >>> isinstance(iter('abc'), Iterator)
4 True

你可能會問,為什么listdictstr等數據類型不是Iterator

這是因為Python的Iterator對象表示的是一個數據流,Iterator對象可以被next()函數調用并不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()函數實現按需計算下一個數據,所以Iterator的計算是惰性的,只有在需要返回下一個數據時它才會計算。

Iterator甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。

小結

凡是可作用于for循環的對象都是Iterable類型;

凡是可作用于next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;

集合數據類型如listdictstr等是Iterable但不是Iterator,不過可以通過iter()函數獲得一個Iterator對象。

Python的for循環本質上就是通過不斷調用next()函數實現的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

實際上完全等價于:

 1 # 首先獲得Iterator對象:
 2 it = iter([1, 2, 3, 4, 5])
 3 # 循環:
 4 while True:
 5     try:
 6         # 獲得下一個值:
 7         x = next(it)
 8     except StopIteration:
 9         # 遇到StopIteration就退出循環
10         break

四、json&pickle序列化

用于序列化的兩個模塊

  • json,用于字符串 和 python數據類型間進行轉換
  • pickle,用于python特有的類型 和 python的數據類型間進行轉換

Json模塊提供了四個功能:dumps、dump、loads、load

pickle模塊提供了四個功能:dumps、dump、loads、load

五、目錄開發規范

假設你的項目名為foo:

Foo/
|-- bin/
|   |-- foo
|
|-- foo/
|   |-- tests/
|   |   |-- __init__.py
|   |   |-- test_main.py
|   |
|   |-- __init__.py
|   |-- main.py
|
|-- docs/
|   |-- conf.py
|   |-- abc.rst
|
|-- setup.py
|-- requirements.txt
|-- README

簡要解釋一下:

  1. bin/: 存放項目的一些可執行文件,當然你可以起名script/之類的也行。
  2. foo/: 存放項目的所有源代碼。(1) 源代碼中的所有模塊、包都應該放在此目錄。不要置于頂層目錄。(2) 其子目錄tests/存放單元測試代碼; (3) 程序的入口最好命名為main.py
  3. docs/: 存放一些文檔。
  4. setup.py: 安裝、部署、打包的腳本。
  5. requirements.txt: 存放軟件依賴的外部Python包列表。
  6. README: 項目說明文件。

關于README的內容

目的是能簡要描述該項目的信息,讓讀者快速了解這個項目。

它需要說明以下幾個事項:

  1. 軟件定位,軟件的基本功能。
  2. 運行代碼的方法: 安裝環境、啟動命令等。
  3. 簡要的使用說明。
  4. 代碼目錄結構說明,更詳細點可以說明軟件的基本原理。
  5. 常見問題說明。