#13 讓代碼變得Pythonic

前言

在學習Python的過程當中,確定據說過這麼一個詞:Pythonic,它的意思是讓你的代碼很Python!python

1、列表生成式

前面有一節專門講解了Python的列表,其靈活的使用方法必定讓你陶醉其中。固然,也也知道怎麼初始化一個列表,好比如今要生成 [0,1,2,3,4] 這樣一個列表:app

In [1]: list(range(5))
Out[1]: [0, 1, 2, 3, 4]

如今要將此列表的每一個元素平方,要怎麼辦呢?函數

方法一:學習

In [9]: a
Out[9]: [0, 1, 2, 3, 4]

In [10]: b = []

In [11]: for i in a:
    ...:     b.append(i**2)
    ...:

In [12]: b
Out[12]: [0, 1, 4, 9, 16]

# 使用 for 循環遍歷每個元素,以後將結果保存在新的列表裏

方法二:大數據

In [13]: a
Out[13]: [0, 1, 2, 3, 4]

In [14]: for index,i in enumerate(a):
    ...:     a[index] **=2
    ...:

In [15]: a
Out[15]: [0, 1, 4, 9, 16]

# 使用內置函數 enumerate() 將可迭代對象返回其索引和相應的值,這種方法直接改變原有列表的元素

方法三:spa

In [15]: a
Out[15]: [0, 1, 4, 9, 16]

In [16]: func = lambda x:x**2

In [18]: a = map(func,a)

In [19]: a
Out[19]: <map at 0x21bbb7a30b8>

In [20]: for i in a:
    ...:     print(i)
    ...:
0
1
16
81
256

# 使用內置函數 map() 也能夠實現,map(函數,可迭代對象),將可迭代對象的每個元素傳入函數並返回結果

方法四:使用更加Pythonic的方法:列表生成式code

In [22]: a = [i for i in range(5)]

In [23]: a
Out[23]: [0, 1, 2, 3, 4]

# 能夠看到生成一個列表就是如此簡單
In [24]: a = [i**2 for i in range(5)]

In [25]: a
Out[25]: [0, 1, 4, 9, 16]

# 能夠看到列表生成式很方便,很好用,這該死的無處安放的魅力啊
In [26]: a = [i for i in range(10) if i%2 == 0]

In [27]: a
Out[27]: [0, 2, 4, 6, 8]

# 列表生成式還能夠加入 if 判斷
In [28]: [p + q for p in range(3) for q in range(5)]
Out[28]: [0, 1, 2, 3, 4, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6]

# 使用兩層循環實現全排列
# 0+0 0+1 0+2 0+3 0+4 0+0 1+0 1+1 1+2 1+3 1+4 2+0 2+1 2+2 2+3 2+4

2、生成器

列表生成式很實用,可是有一個致命的缺點,就是不能建立大數據量的列表,數據量太大時會致使計算機內存不夠用,同時,若是建立的大數據量列表被使用的元素不多的話,那麼就會形成存儲空間的大量浪費,那有沒有一種方法,能夠不提早生成列表,而是在使用列表的時候生成一個列表,換句話說就是:邊循環邊計算,這就是生成器—— generator。生成器在須要的時候才產生結果,不是當即產生結果,生成器效率高,節省CPU生成器只能遍歷一次,是一個特殊的迭代器。對象

1.生成器表達式:相似於列表生成式,只不過將方括號 [] 改變爲圓括號 () blog

In [29]: l = [i for i in range(8)]

In [30]: l
Out[30]: [0, 1, 2, 3, 4, 5, 6, 7]

In [31]: g = (i for i in range(8))

In [32]: g
Out[32]: <generator object <genexpr> at 0x0000021BBBB16E08>

# 能夠看到 l 是列表,而 g 是一個generator

如何得到生成器的元素呢?使用next()方法能夠獲取一個元素:索引

In [33]: next(g)
Out[33]: 0

In [34]: next(g)
Out[34]: 1

In [35]: next(g)
Out[35]: 2

In [36]: next(g)
Out[36]: 3

In [37]: next(g)
Out[37]: 4

In [38]: next(g)
Out[38]: 5

In [39]: next(g)
Out[39]: 6

In [40]: next(g)
Out[40]: 7

In [41]: next(g)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-41-e734f8aca5ac> in <module>
----> 1 next(g)

StopIteration:

# 能夠看到當生成器沒有元素能夠取的時候,會拋出StopIteration異常

能夠看到上面的代碼總司不停的手動使用next()獲取下一個元素,很煩~,在Python中其實不常用next(),而是用for循環的方法迭代生成器:

In [43]: g = (i for i in range(8))

In [45]: for p in g:
    ...:     print(p)
1
2
3
4
5
6
7

建立一個生成器之後,基本上不會使用next()方法,而是使用for循環,迭代完成之後不會拋出StopIteration異常。

2.生成器函數:將函數返回時的關鍵字return改成yield。函數將每次返回一個結果,以後掛起,再次調用時,繼續從掛起的位置執行

In [46]: def print_num():
    ...:     '''
    ...:     print num to screen
    ...:     '''
    ...:     a = 'No.1'
    ...:     b = 'No.2'
    ...:     c = 'No.3'
    ...:     print(a)
    ...:     yield a
    ...:     print(b)
    ...:     yield b
    ...:     print(c)
    ...:     yield c

運行函數可使用 next() ,固然也不經常使用,for循環纔是generator的真愛:

In [46]: def print_num():
    ...:     '''
    ...:     print num to screen
    ...:     '''
    ...:     a = 'No.1'
    ...:     b = 'No.2'
    ...:     c = 'No.3'
    ...:     print(a)
    ...:     yield a
    ...:     print(b)
    ...:     yield b
    ...:     print(c)
    ...:     yield c
    ...:

In [52]: a = print_num()

In [53]: next(a)
No.1
Out[53]: 'No.1'

In [54]: next(a)
No.2
Out[54]: 'No.2'

In [55]: next(a)
No.3
Out[55]: 'No.3'

In [56]: next(a)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-56-15841f3f11d4> in <module>
----> 1 next(a)

StopIteration:
# 菲波那切數列
def Fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b   # 還記得這裏的交換變量方法嗎?
        n = n + 1
    return '到頭了!!!!'

  In [61]: f = Fib(10)

  In [62]: for p in f:
  ...: print(p)
  1
  1
  2
  3
  5
  8
  13
  21
  34
  55

 

  # for循環纔是generator的真愛

3、迭代器

在Python中,list、string、tuple、dict都是可使用for循環進行遍歷的,如今又多了一類generator。這些可使用for循環的對象稱爲可迭代對象。迭代器是用來幫助咱們記錄每次迭代的位置,而可迭代對象使用內置函數iter()是能夠轉換爲迭代器的:

In [63]: a = [1,2,3]      # 建立一個新列表

In [64]: print(a)         # 能夠看到a是列表
[1, 2, 3]  

In [65]: i = iter(a)      # 將其變爲迭代器

In [67]: print(i)         # 能夠看到i爲迭代器
<list_iterator object at 0x0000021BBCE00240>

獲取迭代器中的元素可使用內置函數next(),但不常用,常用的是for循環:

In [68]: i
Out[68]: <list_iterator at 0x21bbce00240>

In [70]: for p in i:
    ...:     print(p)
1
2
3

補充:對於列表、字符串、元組、字典等數據類型,在使用for循環時,在後臺for語句對這些對象調用iter()函數,以後使用next()逐個訪問每個元素,直到遇到StopIteration異常,迭代結束。

在Python中,可使用 isinstance() 判斷一個對象是否爲可迭代對象:

In [71]: from collections import Iterable   # 導入Iterable模塊,以後會講

In [72]: isinstance([],Iterable)
Out[72]: True

In [73]: isinstance((),Iterable)
Out[73]: True

In [74]: isinstance({},Iterable)
Out[74]: True

In [75]: isinstance('',Iterable)
Out[75]: True

4、裝飾器

裝飾器是什麼呢?來舉個例子就明白了:有一個長髮飄飄的漂亮女明星,被邀出演尼姑,確定不會把頭髮剃光了吧,怎麼辦呢,聰明的你必定想到戴個頭套就行。是的,在Python中,長髮飄飄的女明星就是源代碼,頭套就是裝飾器。轉時期的本質就是在不改變函數原有代碼而且不改變原有函數的調用方式的基礎上給函數加上新的功能,聽起來很迷人,用起來同樣有趣,讓你的代碼一會兒就提升檔次了。

1.過程No.1

如今有一個 tell_name() 函數:

1 def tell_name():
2     print('I am MinuteSheep')

要求記錄它的執行時間,對原有函數改寫,這樣來實現:

1 import time    # 引入time模塊,這是一個時間模塊,之後會講到
2 def tell_name():
3     start_time = time.time()
4     print('I am MinuteSheep')
5     time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒
6     end_time = time.time()
7     print('執行時間爲:',end_time - start_time)
8 
9 tell_name()

# 運行結果:

  I am MinuteSheep
  執行時間爲: 2.001427173614502

2.過程No.2

如今又100個函數須要計算其執行時間,總不能改寫100個函數的源代碼吧,怎麼辦呢?還記的高階函數嗎,能夠講函數看成變量傳給函數:

import time    # 引入time模塊,這是一個時間模塊,之後會講到


def tell_name():
    print('I am MinuteSheep')
    time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒


def Time(func):  # 使用高階函數
    start_time = time.time()
    func()
    end_time = time.time()
    print('執行時間爲:', end_time - start_time)

Time(tell_name)   # 調用方式發生改變

# 運行結果:

  I am MinuteSheep
  執行時間爲: 2.00026535987854

上面代碼彷佛實現了這個功能,也沒有修改原函數的代碼,可是卻改變了它的調用方式,若是一個程序中有上百條調用,都要改的話仍是很麻煩

3.過程No.3

import time    # 引入time模塊,這是一個時間模塊,之後會講到


def tell_name():
    print('I am MinuteSheep')
    time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒


def Time(func):
    def wrapper():    # 使用函數的嵌套
        start_time = time.time()
        func()
        end_time = time.time()
        print('執行時間爲:', end_time - start_time)
    return wrapper


tell_name = Time(tell_name)   # 至關於 tell_name = wrapper
tell_name()                            # 至關於執行 wrapper()

上面代碼已經基本實現了這個功能,可是每次都要寫兩條調用語句才行,很煩

4.過程No.4

在Python中,爲了克服上述問題,出現了一個叫作語法糖的語句,因此裝飾器又叫作語法糖,在函數定義以前使用@語法糖可增長相應的功能

import time    # 引入time模塊,這是一個時間模塊,之後會講到


def Time(func):
    def wrapper():    # 使用函數的嵌套
        start_time = time.time()
        func()
        end_time = time.time()
        print('執行時間爲:', end_time - start_time)
    return wrapper


@Time   # 這就是裝飾器,也叫語法糖
def tell_name():
    print('I am MinuteSheep')
    time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒


tell_name()  # 至關於執行 wrapper()

# 運行結果:

  I am MinuteSheep
  執行時間爲: 2.000563621520996

上面代碼實現了一個最簡單的裝飾器。

5.過程No.5

可是,又有新的問題出現了,若是被裝飾函數有參數怎麼辦,這麼辦:

import time    # 引入time模塊,這是一個時間模塊,之後會講到


def Time(func):
    def wrapper(name):    # 使用函數的嵌套
        start_time = time.time()
        func(name)
        end_time = time.time()
        print('執行時間爲:', end_time - start_time)
    return wrapper


@Time
def tell_name(name):
    print('I am',name)
    time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒


tell_name('MS')  # 至關於執行 wrapper('MS')

# 運行結果:

  I am MS
  執行時間爲: 2.0003795623779297

看起來不錯

6.過程No.6

上面代碼實現了裝飾有一個參數函數的功能,可是,裝飾器被應用與不一樣的函數,誰能知道這個函數有沒有參數,有幾個參數,爲了實現通用性,這麼辦:

import time    # 引入time模塊,這是一個時間模塊,之後會講到


def Time(func):
    def wrapper(*args, **kwargs):    # 經過非固定參數實現各類參數的通用裝飾器
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print('執行時間爲:', end_time - start_time)
    return wrapper


@Time
def tell_name(name):
    print('I am', name)
    time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒


@Time
def add(a, b):
    c = a + b
    print(c)


tell_name('MS')  
add(5, 6)

# 運行結果:

  I am MS
  執行時間爲: 2.00108003616333
  11
  執行時間爲: 0.0004711151123046875

7.過程No.7

上面的過程當中裝飾器沒有參數,其實裝飾器時能夠帶參數的:

import time    # 引入time模塊,這是一個時間模塊,之後會講到


def Time(num):   # 使用兩層嵌套實現帶參數的裝飾器
    def decorator(func):
        def wrapper(*args, **kwargs):
            if num == 1:
                start_time = time.time()
                func(*args, **kwargs)
                end_time = time.time()
                print('執行時間爲:', end_time - start_time)
            elif num == 0:
                func(*args, **kwargs)
                print('不須要計算時間')
        return wrapper
    return decorator


@Time(num=1)
def tell_name(name):
    print('I am', name)
    time.sleep(2)  # 爲了體現執行時間,讓程序等兩秒


@Time(num=0)
def add(a, b):
    c = a + b
    print(c)


tell_name('MS')
add(5, 6)

# 運行結果:

  I am MS
  執行時間爲: 2.0000314712524414
  11
  不須要計算時間

8.過程No.8

一個函數可使用多個裝飾器,裝飾器運行順序從裏到外:

@a
@b 
@c  
def func():
    pass

# 先運行c,再運行b,最後運行a

以上就是裝飾器99%的功能,還有一種叫作類裝飾器,等記錄完Python面向對象的知識後再補充,拜拜~

相關文章
相關標籤/搜索