文章出處

Python自動化 【第四篇】:Python基礎-裝飾器 生成器 迭代器 Json & pickle

目錄:

  1. 裝飾器
  2. 生成器
  3. 迭代器
  4. Json & pickle 數據序列化
  5. 軟件目錄結構規范

 

1. Python裝飾器

  裝飾器:本質是函數,(功能是裝飾其它函數)就是為其他函數添加附加功能

 

  原則:

    >不能修改被裝飾的函數的源代碼

    >不能修改被裝飾的函數的調用方式

 

  實現裝飾器知識儲備:

    >函數即“變量” 

    >高階函數

      a)把一個函數名當做實參傳給另外一個函數(在不修改源代碼的請情況下)

      b)返回值中包含函數名(不修改函數的調用方式)

    >嵌套函數

       高階函數+嵌套函數=》裝飾器

  

1.1 函數的調用順序:

  Python不允許函數在未聲明之前對其進行引用或者調用

    錯誤案例一:

    
def foo():

    print 'in the foo'

    bar()

foo()
View Code

    錯誤案例二:

    
def foo():

    print 'foo'

    bar()

foo()

def bar():

    print 'bar'
View Code

  以上兩個案例都會報錯:NameError: global name 'bar' is not defined

    正確案例一:

    
def bar():

    print 'in the bar'

def foo():

    print 'in the foo'

    bar()

   foo()
View Code

    正確案例二:

    
def foo():

    print 'in the foo'

    bar()

def bar():

    print 'in the bar'

foo()
View Code

  (python為解釋執行,函數foo在調用前已經聲明了bar和foo,所以bar和foo無順序之分)

1.2  高階函數

  高階函數需滿足以下兩個條件:

    a)某一函數當做參數傳入另一函數中(在不修改源代碼的請情況下)

    b)返回值中包含函數名(不修改函數的調用方式)

  高階函數示例:

    
def bar():
    print("In the bar")
def foo(func):
    res = func()
    return res
foo(bar)
View Code

  高階函數進階:

    
         def foo(func):

    return func

 

print 'Function body is %s' %(foo(bar))

print 'Function name is %s' %(foo(bar).func_name)

foo(bar)()

#foo(bar)() 等同于bar=foo(bar)然后bar()

bar=foo(bar)

bar()
View Code

1.3 內嵌函數和變量作用域

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

  函數嵌套示范:

    
def foo():
    def bar():
        print
        'in the bar'
    bar()
foo()
View Code

  局部作用域和全局作用域的訪問順序

    
x=0
def grandpa():
    # x=1
    def dad():
        x=2
        def son():
            x=3
            print x
        son()
    dad()
grandpa()
View Code

    范例一:函數參數固定

    
def decorartor(func):
    def wrapper(n):
        print
        'starting'
        func(n)
        print
        'stopping'
    return wrapper
def test(n):
    print
    'in the test arg is %s' % n
decorartor(test)('Tom')
View Code

    范例二:函數參數不固定

    
def decorartor(func):
    def wrapper(*args, **kwargs):
        print
        'starting'
        func(*args, **kwargs)
        print
        'stopping'
    return wrapper
def test(n, x=1):
    print
    'in the test arg is %s' % n
decorartor(test)('alex', x=2)
View Code

  無參裝飾器

  
import time
def decorator(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        stop = time.time()
        print
        'run time is %s ' % (stop - start)
        print
        timeout
    return wrapper
@decorator
def test(list_test):
    for i in list_test:
        time.sleep(0.1)
        print
        '-' * 20, i
# decorator(test)(range(10))
test(range(10))
View Code

  有參裝飾器

  
import time
def timer(timeout=0):
    def decorator(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            func(*args, **kwargs)
            stop = time.time()
            print
            'run time is %s ' % (stop - start)
            print
            timeout
        return wrapper
    return decorator
@timer(2)
def test(list_test):
    for i in list_test:
        time.sleep(0.1)
        print
        '-' * 20, i
# timer(timeout=10)(test)(range(10))
test(range(10))
View Code

 

2. 生成器

  a = [i*2 for I in range(10)]   #列表生成式

  一邊循環一邊計算的機制,稱為生成器:generator

  要創建一個generator,有很多種方法。

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

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

 

  創建Lg的區別僅在于最外層的[]()L是一個list,而g是一個generator。

  如果要一個一個打印出來,可以通過next()函數獲得generator的下一個返回值:

  
>>> next(g)

0

>>> next(g)

1

>>> next(g)

4

>>> next(g)

9

>>> next(g)

16

>>> next(g)

25

>>> next(g)

36

>>> next(g)

49

>>> next(g)

64

>>> next(g)

81

>>> next(g)

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

StopIteration
View Code

 

  generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration的錯誤。

  當然,上面這種不斷調用next(g)實在是太變態了,正確的方法是使用for循環,因為generator也是可迭代對象

  
>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
...
0
1
4
9
16
25
36
49
64
81
View Code

 

  所以,我們創建了一個generator后,基本上永遠不會調用next(),而是通過for循環來迭代它,并且不需要關心StopIteration的錯誤。

  generator非常強大。如果推算的算法比較復雜,用類似列表生成式的for循環無法實現的時候,還可以用函數來實現 

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

  
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'
View Code

  上面的函數可以輸出斐波那契數列的前N個數:

  
>>> fib(10)
1
1
2
3
5
8
13
21
34
55
done
View Code

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

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

  
def fib(max):

    n,a,b = 0,0,1

 

    while n < max:

        #print(b)

        yield  b

        a,b = b,a+b

 

        n += 1

 

    return 'done'
View Code

 

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

  
>>> f = fib(6)

>>> f

<generator object fib at 0x104feaaa0>
View Code

 

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

  
data = fib(10)

print(data)

 

print(data.__next__())

print(data.__next__())

print("干點別的事")

print(data.__next__())

print(data.__next__())

print(data.__next__())

print(data.__next__())

print(data.__next__())
View Code

  #輸出

  
<generator object fib at 0x101be02b0>

1

1

干點別的事

2

3

5

8

13
View Code

 

  在上面fib的例子,我們在循環過程中不斷調用yield,就會不斷中斷。當然要給循環設置一個條件來退出循環,不然就會產生一個無限數列出來。

  同樣的,把函數改成generator后,我們基本上從來不會用next()來獲取下一個返回值,而是直接使用for循環來迭代:

  
>>> for n in fib(6):

...     print(n)

...

1

1

2

3

5

8
View Code

 

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

  
>>> g = fib(6)

>>> while True:

...     try:

...         x = next(g)

...         print('g:', x)

...     except StopIteration as e:

...         print('Generator return value:', e.value)

...         break

...

g: 1

g: 1

g: 2

g: 3

g: 5

g: 8

Generator return value: done
View Code

 

  還可通過yield實現在單線程的情況下實現并發運算的效果

  
import time
def consumer(name):
    print("%s 準備吃包子啦!" %name)
    while True:
       baozi = yield

       print("包子[%s]來了,被[%s]吃了!" %(baozi,name))


def producer(name):
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()
    c2.__next__()
    print("老子開始準備做包子啦!")
    for i in range(10):
        time.sleep(1)
        print("做了2個包子!")
        c.send(i)
        c2.send(i)

producer("Tom")
View Code

 

3. 迭代器

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

    一類是集合數據類型,如list、tuple、dict、set、str等;

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

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

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

  
>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False
View Code

  而生成器不但可以作用于for循環,還可以被next()函數不斷調用并返回下一個值,直到最后拋出StopIteration錯誤表示無法繼續返回下一個值了。

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

 

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

  
 1 >>> from collections import Iterator
 2 
 3 >>> isinstance((x for x in range(10)), Iterator)
 4 
 5 True
 6 
 7 >>> isinstance([], Iterator)
 8 
 9 False
10 
11 >>> isinstance({}, Iterator)
12 
13 False
14 
15 >>> isinstance('abc', Iterator)
16 
17 False
View Code

  生成器都是Iterator對象,但list、dict、str雖然是Iterable,卻不是Iterator。

  把list、dict、str等Iterable變成Iterator可以使用iter()函數:

  
>>> isinstance(iter([]), Iterator)

True

>>> isinstance(iter('abc'), Iterator)

True
View Code

 

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

  小結:

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

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

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

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

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

    實際上完全等價于:

    
# 首先獲得Iterator對象:

it = iter([1, 2, 3, 4, 5])

# 循環:

while True:

    try:

        # 獲得下一個值:

        x = next(it)

    except StopIteration:

        # 遇到StopIteration就退出循環

        break
View Code

 

4. json和pickle

  用于序列化的兩個模塊

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

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

  

5. 軟件目錄結構規范

  設計軟件目錄結構為了達到以下兩點:

  >可讀性高: 不熟悉這個項目的代碼的人,一眼就能看懂目錄結構,知道程序啟動腳本是哪個,測試目錄在哪兒,配置文件在哪兒等等。從而非常快速的了解這個項目。

  >可維護性高: 定義好組織規則后,維護者就能很明確地知道,新增的哪個文件和代碼應該放在什么目錄之下。這個好處是,隨著時間的推移,代碼/配置的規模增加,項目結構不會混亂,仍然能夠組織良好。

  目錄組織方式:

  假設你的項目名為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
View Code

 

  簡要解釋一下:

  >bin/: 存放項目的一些可執行文件,當然你可以起名script/之類的也行。

  >foo/: 存放項目的所有源代碼。(1) 源代碼中的所有模塊、包都應該放在此目錄。不要置于頂層目錄。(2) 其子目錄tests/存放單元測試代碼; (3) 程序的入口最好命名為main.py。

  >docs/: 存放一些文檔。

  >setup.py: 安裝、部署、打包的腳本。

  >requirements.txt: 存放軟件依賴的外部Python包列表。

  >README: 項目說明文件。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()