Python 高級進階知識(一)

參考 Python學習手冊 第四版

1 from vs import

  • import 模塊 : 導入的一整個模塊(python中模塊對應一個py文件)

    因為import使用一個變量名引用整個模塊對象,所以必須通過模塊名稱來得到該模塊的屬性。

    程序在第一次導入指定模塊的時候,會執行三個步驟

    1. 找到模塊文件
    2. 編譯成位元組碼(需要時)
    3. 執行模塊的代碼來創建其所定義的對象

    這三個步驟,只在模塊第一次被導入時才進行,第二次導入則不會,只會提取內存中已加載的模塊對象。

    從技術上講,python將載入的模塊存儲到一個名為“sys.modules`的表中,並在一次導入操作的開始檢查該表,如果模塊不存在,將會啟動這三個步驟。

  • from... import : 導入模塊中的一個變量

    因為from會把變量名複製到另一個作用域,所以就可以直接在腳本中使用複製後的變量名,而不需要通過模塊訪問

舉個粒子

先建立一個ttttest.py腳本文件,輸入一行代碼。

cnblog_name = "XiiX" 

使用import需要通過模塊名稱來訪問,導入的是一個模塊

In [6]: import ttttest

In [7]: ttttest.cnblog_name
Out[7]: 'XiiX'
    
In [8]: type(ttttest)
Out[8]: module

使用from可以直接訪問,可以看到導入的就是一個變量

In [10]: from ttttest import cnblog_name

In [11]: cnblog_name
Out[11]: 'XiiX'

In [12]: type(cnblog_name)
Out[12]: str

import和from其實是一個賦值語句

請注意,它們不是編譯期間的聲明,而是一個可以執行的語句。

  • import 將整個模塊對象賦值給一個變量名
  • form 將一個或多個變量名賦值給另一個模塊中同名的對象

2 列表解析

在處理序列的操作和列表的方法中,python有一個更高級的操作——列表解析表達式(list comprehension expression)。列表解析常常具有處理速度上的優勢(會比手動的for循環語句快一倍以上,因為它們的迭代在解釋器內存是以C語言的速度執行的而不是以手動python代碼執行),能夠在python的任何序列類型中發揮作用,甚至一些不屬於序列的類型。

雖然列表解析速度上很有優勢,但是只適用於小規模的任務,對於大規模的數值運算,使用Numpy包的效率會更高。

舉個粒子:

  • 假設我們需要從矩陣中提取出第二列,我們可以通過一行代碼就能解決這個問題

    In [2]: M
    Out[2]: [[1, 2, 4], [2, 5, 6], [1, 2, 3]]
    
    In [3]: col2 = [row[1] for row in M]
    
    In [4]: col2
    Out[4]: [2, 5, 2]
    

列表解析源自集合的概念,是一種通過對序列中的每一項運行一個表達式來創建一個新列表的方法。列表解析是編寫在方括號中的(提醒你在創建一個新列表),並且使用了同一個變量名(這裡是row)的表達式和循環結構組成。

實際操作中,我們還可以加一些額外的操作,比如算術運算,過濾等操作

# +1
In [5]: [row[1] +1 for row in M]
Out[5]: [3, 6, 3]

# 只取出偶數
In [6]: [row[1] for row in M if row[1]%2==0 ]
Out[6]: [2, 2]

再舉一些粒子,大家也可以發揮自己的想像力

  • 取出矩陣中的對角線上的值,非常的方便

    In [8]: [M[i][i] for i in range(len(M))]
    Out[8]: [1, 5, 3]
    
  • 分離字符串,不過list()也能做

    In [9]: [c+',' for c in "Hello"]
    Out[9]: ['H,', 'e,', 'l,', 'l,', 'o,']
    
    In [10]: list('Hello')
    Out[10]: ['H', 'e', 'l', 'l', 'o']
    

還可以使用嵌套的循環

In [13]: [x+y for x in "abc" for y in "XiiX"]
Out[13]: ['aX', 'ai', 'ai', 'aX', 'bX', 'bi', 'bi', 'bX', 'cX', 'ci', 'ci', 'cX']

3 range迭代器

python3.0中,range返回一個迭代器,該迭代器根據需要產生範圍內的數字,而不是在內存中構建一個結果列表。會比較節省空間。

如果需要一個列表的話,可以使用list(range(...))強制產生一個真正的範圍列表

In [15]: r = range(0,10)

In [16]: r
Out[16]: range(0, 10)

In [17]: list(r)
Out[17]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

python3.0中,range對象只支持迭代、索引以及len函數,不支持其他的序列操作。

In [18]: len(r)
Out[18]: 10

In [19]: r[0]
Out[19]: 0

In [20]: r[2]
Out[20]: 2

In [22]: I = iter(r)

In [23]: next(I)
Out[23]: 0

In [24]: next(I)
Out[24]: 1

4 map、zip和filter迭代器

和range類似,,map、zip以及filter內置函數在Python3.0中也轉變為迭代器以節約內存空間。

但是和range不同,它們都是自己的迭代器——在遍歷其結果一次之後,它們就用盡了,換句話說,不能在它們的結果上引用在那些結果中保持不同位置的多個迭代器。

In [35]: m = map(abs, (-2,-1,0))

In [36]: m
Out[36]: <map at 0x7fb371ca4130>

In [37]: next(m)
Out[37]: 2

In [38]: next(m)
Out[38]: 1

In [39]: next(m)
Out[39]: 0

In [40]: next(m)
StopIteration

可以看到,在遍歷一次之後,它們就用盡了。無法再繼續遍歷。

如果還是不明白的話,我們再來觀察一下對zip的試驗。可以看到兩次迭代之後,用list強制轉換z,之後只剩一個值, 所以值是在消耗的,一旦迭代一次過後,這個值就沒有了。

注意:list(z)會一次性將其全部消耗

In [49]: z = zip((1,2,3),(10,20,30))

In [50]: next(z)
Out[50]: (1, 10)

In [51]: next(z)
Out[51]: (2, 20)

In [52]: list(z)
Out[52]: [(3, 30)]
    
In [53]: next(z)
StopIteration: 

多個迭代器vs單個迭代器

range可以同時存在多個迭代器,並且支持len和索引,但是不是自身的迭代器,是使用iter產生的迭代器。

可以看到range本身沒有迭代器,要使用iter產生,產生的迭代器之間互不干擾是獨立的。

In [54]: r = range(3)

In [55]: next(r)
TypeError: 'range' object is not an iterator

In [56]: I1 = iter(r)

In [57]: I2 = iter(r)

In [58]: next(I1)
Out[58]: 0

In [59]: next(I2)
Out[59]: 0

相反,zip和map和filter之間不支持相同結果上的多個迭代器同時存在。

從下方試驗的代碼可以看到,即使定義了兩個迭代器,但是由於迭代是不斷消耗的(與range不同),所以兩個迭代器實際上指向的位置是一致的。

In [60]: z = zip((1,2,3),(1,2,3))

In [61]: I1 = iter(z)

In [62]: I2 = iter(z)

In [63]: next(I1)
Out[63]: (1, 1)

In [64]: next(I1)
Out[64]: (2, 2)

In [65]: next(I2)
Out[65]: (3, 3)

In [66]: next(I2)
StopIteration: 

In [67]: next(I1)
StopIteration: 

map

在程序中對列表和其他序列常常要做的一件事就說對每一個元素進行一個操作,並把其結果集合起來,這樣的情況就可以使用map來進行操作。

例如,在一個列表counter中更新所有的數字,這雖然可以寫一個for循環,完成,但是通過map會更加高效。

lambda的使用見下一節

In [5]: counter = [1,2,3,4]

## for
In [6]: temp = []

In [7]: for x in counter:
   ...:     temp.append(x + 10)
   ...: 

In [8]: temp
Out[8]: [11, 12, 13, 14]
    
## map
In [9]: list(map(lambda x:x+10, counter))
Out[9]: [11, 12, 13, 14]

zip

使用zip可以配對來自多個序列的參數

In [12]: list(zip((1,2,3),(4,5,6),(7,8,9)))
Out[12]: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

filter

函數式編程工具filter,在python的內置函數中,map函數是用來進行函數式編程的這類工具中最簡單的內置函數代表。

函數式編程的意思就是對序列應用一些函數的工具,例如,基於某一測試函數過濾出一些元素(filter)

舉個例子,從一個序列中挑選出大於0的元素

In [11]: list(filter( (lambda x: x>0), range(-5,3) ))
Out[11]: [1, 2]

5 lambda表達式

lambda名稱是來自LISP,而LISP則是從lambda calculus (一種符號邏輯形式)取得到的名稱。

除了def以外,python還提供了一種生成函數對象的表達式形式,lambda。就像def一樣,這個表達式創建了一個能夠調用的函數,但是它返回了一個函數而不是將這個函數賦值給一個變量名。這也是lambda有時也叫做匿名函數的原因。

lambda表達式的一般寫法

lambda argument1, argument1, ... argumentN : expression using arguments
  • lambda是一個表達式,而不是一個語句。因為這一點,lambda可以出現在python語法不允許def出現的地方——例如,在一個列表常量中或者函數調用的參數中。
  • lambda的主體是一個單個的表達式,而不是一個代碼塊。主體非常簡單,也決定了其不能執行複雜的任務
In [68]: def func(x,y,z): return x+y+z

In [69]: func(1,24,1)
Out[69]: 26

In [70]: f = lambda x,y,z:x+y+z

In [71]: f(2,3,4)
Out[71]: 9

這裡的f被賦值給一個lambda表達式創建的函數對象,這也就是def所完成的任務。只不過def的賦值是自動進行的。

此外,默認參數也能夠在lambda參數中使用

In [72]: x = (lambda a='fee', b='fie', c='foe': a+b+c)

In [73]: x("wee")
Out[73]: 'weefiefoe'

為什麼要使用lambda?

總體而言,使用lambda起到了一種函數速寫的作用,允許在使用的代碼內嵌入一個函數的定義。雖然能用def來代替,但是使用lambda會更簡潔。

lambda通常用來編寫跳轉表(jump table),也就是行為的列表或者字典。

In [1]: L = [lambda x : x**2, lambda x : x**3, lambda x : x**4 ]

In [2]: for f in L:
   ...:     print(f(2))
   ...: 
4
8
16

像這樣的情況,def無法直接寫入列表,只能先定義好三個def,然後再創建列表,毫無疑問,使用lambda會更為高效。

還可以寫入字典里

In [3]: dict = {'already':(lambda:2+2), 'got':(lambda:2*4),'one':(lambda:2**3)}

In [4]: dict['got']()
Out[4]: 8

下一章會講一下python的對象