天天看點

Python進階筆記疊代器

  1. 清單生成式
  2. 函數的參數類型
  3. lambda函數
  4. map, reduce, filter, sorted函數
  5. eval, exec, join, zip函數
  6. itertools中的函數
  7. copy與deepcopy函數
  8. 子產品
  9. os、sys子產品
  10. 疊代器
  11. 生成器

參考網站:

  1. Python3教程: https://www.python-course.eu/python3_course.php
  2. Python之函數參數的使用: https://blog.csdn.net/jclian91/article/details/78309522
  3. 廖雪峰Python教程: https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000
  4. Python之淺談exec函數: https://blog.csdn.net/jclian91/article/details/80076512
  5. Python官網的itertools說明: https://docs.python.org/3.6/library/itertools.html
  6. Python-copy()與deepcopy()差別: https://blog.csdn.net/qq_32907349/article/details/52190796
  7. copy子產品官網: https://docs.python.org/3.5/library/copy.html

清單生成式即List Comprehensions,是Python内置的非常簡單卻強大的可以用來建立list的生成式。一般是利用原有的資料結構來生成新的清單。

# 利用range()生成[1,2,...,9,10]
list(range(1,11))

# 生成[1x1, 2x2, 3x3, ..., 10x10]
[x * x for x in range(1, 11)]
# 可以通過占位符_代表清單中的元素
[_*_ for _ in range(1,11)]

# 篩選出僅偶數的平方, 在for循環後加上if判斷語句
[x * x for x in range(1, 11) if x % 2 == 0]
# 利用占位符簡化
[_*_ for _ in range(1, 11) if not _%2]

# 兩層循環,三層循環,....
[m + n for m in 'ABC' for n in 'XYZ']
[x+y+z for x in  'ab' for y in 'cd' for z in 'ef']

# 周遊字典,生成清單
d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]           

在Python中定義函數,其參數類型有:

- 位置參數

- 預設參數

- 可變參數

- 關鍵字參數

這4種參數都可以一起使用,或者隻用其中某些,但是請注意,參數定義的順序必須是:位置參數、預設參數、可變參數和關鍵字參數。

可變參數以*開頭,允許傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple。關鍵字參數以**開頭,允許傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個dict。若預設參數與可變參數放在一起,則接受完預設參數後,其後參數為可變參數。

位置參數

位置參數指定名稱的必須放在未指定名稱的後面

def person(name,age,city):
    s = "info: name=%s, age=%s, city=%s"%(name,age,city)
    return s

print(person('Jack', 25, 'NY'))
print(person(name='Jack', age=25, city='NY'))
print(person('Jack', 25, city='NY'))
# 下面的參數使用有誤,位置參數指定名稱的必須放在未指定名稱的後面
print(person(name='Jack', 25, 'NY'))           

預設參數

預設參數必須放在非預設參數的後面,可以該表預設參數的值

def person(name, city, age=18):
    s = "info: name=%s, age=%s, city=%s"%(name,age,city)
    return s

print(person('Jack', 'NY'))
print(person('Jack', 'NY', 20))           

可變參數

可變參數以*開頭,允許傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個tuple。函數參數的長度是可以變化的, 例如内置的sum, min, max等

def var_sum(*args):
    sum = 0
    for i in args:
        sum += i

    return sum

print(var_sum(1,2,3))
print(var_sum(1,2,3,4))
# 利用*号來分解參數
print(var_sum(*[1,2,3,4,5]))           

若位置參數或預設參數與可變參數放在一起,則接受完位置參數或預設參數後,其後參數為可變參數。

def var_sum(a, *args):
    sum = 0
    for i in args:
        sum += i

    print('a is %s, sum is %s'%(a,sum))

var_sum(1,2)
var_sum(1,2,3)           

關鍵字參數

關鍵字參數以**開頭,允許傳入0個或任意個參數,這些可變參數在函數調用時自動組裝為一個dict。

def test_args(**kwargs):
    print('-'*20)
    for key in kwargs:
        print('key:', key, ',value:', kwargs[key])

    print()

test_args(a=1,b=2)
test_args(a=1,b=2,c=3)           

lambda函數即為匿名函數,用關鍵字lambda表示,冒号(:)前面的為參數,後面為傳回值,不用寫return.

如:

lambda x: x*x           

匿名函數有個限制,就是隻能有一個表達式,一般一行代碼,不用寫return,傳回值就是該表達式的結果。

用匿名函數有個好處,因為函數沒有名字,不必擔心函數名沖突。此外,匿名函數也是一個函數對象,也可以把匿名函數指派給一個變量,再利用變量來調用該函數,即函數也是變量,此為函數式程式設計(functional programming)思想。

f = lambda x: x*x
f(5)           

map函數

map()函數接收兩個參數,一個是函數,一個是Iterable,map将傳入的函數依次作用到序列的每個元素,并把結果作為新的Iterator傳回。

可以直接作用于for循環的對象統稱為可疊代對象:Iterable.

舉例說明,比如我們有一個函數f(x)=x^2,要把這個函數作用在一個list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()實作如下:

# map函數: 一一映射
def f(x):
    return x * x

r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)

# 利用lambda簡化上述代碼

list(map(lambda x: x*x, range(1, 11)))
           

再例如: 把list所有數字轉為字元串:

list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))           

reduce函數

reduce把一個函數作用在一個序列[x1, x2, x3, …]上,這個函數必須接收兩個參數,一個是函數,一個是Iterable. reduce把結果繼續和序列的下一個元素做累積計算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方說對一個序列求和,就可以用reduce實作:

# 導入reduce, 這很重要
from functools import reduce

def add(x, y):
    return x + y

reduce(add, [1, 3, 5, 7, 9])

# 利用lambda函數簡化
reduce(lambda x,y: x+y, range(1,10,2))           

作業: 利用reduce将序列[1, 3, 5, 7, 9]轉化為整數13579.

map, reduce的一個複雜例子:

将字元串清單[‘1’, ‘3’, ‘5’, ‘7’, ‘9’]轉化為整數13579

from functools import reduce

a = ['1', '3', '5', '7', '9']
t = reduce(lambda x,y: 10*x+y, map(int, a))
print(t)           

filter函數

Python内建的filter()函數用于過濾序列。

和map()類似,filter()也接收一個函數和一個序列。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然後根據傳回值是True還是False決定保留還是丢棄該元素。

例如,在一個list中,删掉偶數,隻保留奇數,可以這麼寫:

list(filter(lambda x: x%2 == 1, [1, 2, 4, 5, 6, 9, 10, 15]))           

sorted函數

Python内置的sorted()函數就可以對list進行排序。

sorted([36, 5, -12, 9, -21])           

此外,sorted()函數還可以接收一個key函數來實作自定義的排序,例如按絕對值大小排序:

sorted([36, 5, -12, 9, -21], key=abs)
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
           

高階函數,就是讓函數的參數能夠接收别的函數。map, reduce, filter, sorted都是高階函數。

join, zip, eval, exec函數

join函數

Python中的join函數有兩個,分别為: join()和os.path.join(),具體作用如下:

  • join(): 連接配接字元串數組。将字元串、元組、清單中的元素以指定的字元(分隔符)連接配接生成一個新的字元串
  • os.path.join(): 将多個路徑組合後傳回

字元串中的join()函數的使用方法:

‘sep’.join(seq)

sep:分隔符。可以為空。 seq:要連接配接的元素序列。 傳回一個新的字元串。

seq = ['hello','good','boy','Dido']

print(' '.join(seq))
print('*'.join(seq))           

zip函數

zip() 函數用于将可疊代的對象作為參數,将對象中對應的元素打包成一個個元組,然後傳回由這些元組組成的清單。

如果各個疊代器的元素個數不一緻,則傳回清單長度與最短的對象相同,利用 * 号操作符,可以将元組解壓為清單。

# basic use of zip
x = [1, 2, 3]
y = [4, 5, 6]
zipped = zip(x, y)
print(list(zipped))

# zip for loops
for i,j in zip(x,y):
    print(i, "->", j)

# unzip the list
a = [(1,2,3), (3,4,5)]
x2, y2, z2 = zip(*a)
print(x2)
print(y2)
print(z2)

# transpose a matrix
mtx = [(1, 2),
       (3, 4),
       (5, 6)]
print(list(zip(*mtx)))

# clustering a data series into n-length groups idiom
seq = range(1, 10)
print(list(zip(*[iter(seq)]*3)))

# dict and zip
keys = ['spam', 'eggs']
vals = [42, 1729]
d = dict(zip(keys, vals))
print(d)
           

eval函數

eval函數用來計算字元串表達式的值

t = eval("23")
print(t)
print(type(t))

print(eval("(1+2)*(3+4)"))           

exec函數

exec()是Python的内置函數,不同于eval()函數隻能執行計算數學表達式的結果的功能,exec()能夠動态地執行複雜的Python代碼,能夠十分強大。

簡單例子:

# 執行簡單的Python語句
i = 12
j = 13
exec("answer=i*j")
print("Answer is %s"%answer)

# 執行複雜的Python語句
func = "def fact(n):\n\treturn 1 if n==1 else n*fact(n-1)"
exec(func)
a = fact(5)
print(a)           

exec函數還可以執行儲存在其他檔案中的Python代碼,例如位于E盤的eg.txt,如下:

def fact(n):
    if n==1:
        return 1
    else:
        return n*fact(n-1)
t = fact(6)
print(t)           

利用exec函數執行eg.txt中的代碼:

with open('E://eg.txt', 'r') as f:
    s = f.read()

exec(s)           

還可以在exec()函數中加入參數,參數的傳遞可以寫成字典(dict)形式。

x = 10

expr = """
z = 30
sum = x + y + z
print(sum)
"""

def func():
    y = 20
    exec(expr)
    exec(expr, {'x': 1, 'y': 2})
    exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})

func()           

輸出結果為:

60 
33 
34
      

itertools子產品中的函數

Python的内模組化塊itertools提供了非常有用的用于操作疊代對象的函數。

itertools子產品提供的全部是處理疊代功能的函數,它們的傳回值不是list,而是Iterator,隻有用for循環疊代的時候才真正計算。

無窮疊代器

Iterator Arguments Results Example
count() start, [step] start, start+step, start+2*step, … count(10) –> 10 11 12 13 14 …
cycle() p p0, p1, … plast, p0, p1, … cycle(‘ABCD’) –> A B C D A B C D …
repeat() elem [,n] elem, elem, elem, … endlessly or up to n times repeat(10, 3) –> 10 10 10

“有限”疊代器

accumulate() p [,func] p0, p0+p1, p0+p1+p2, … accumulate([1,2,3,4,5]) –> 1 3 6 10 15
chain() p, q, … p0, p1, … plast, q0, q1, … chain(‘ABC’, ‘DEF’) –> A B C D E F
chain.from_iterable() iterable chain.from_iterable([‘ABC’, ‘DEF’]) –> A B C D E F
compress() data, selectors (d[0] if s[0]), (d[1] if s[1]), … compress(‘ABCDEF’, [1,0,1,0,1,1]) –> A C E F
dropwhile() pred, seq seq[n], seq[n+1], starting when pred fails dropwhile(lambda x: x<5, [1,4,6,4,1]) –> 6 4 1
filterfalse() elements of seq where pred(elem) is false filterfalse(lambda x: x%2, range(10)) –> 0 2 4 6 8
groupby() iterable[, keyfunc] sub-iterators grouped by value of keyfunc(v)
islice() seq, [start,] stop [, step] elements from seq[start:stop:step] islice(‘ABCDEFG’, 2, None) –> C D E F G
starmap() func, seq func(*seq[0]), func(*seq[1]), … starmap(pow, [(2,5), (3,2), (10,3)]) –> 32 9 1000
takewhile() seq[0], seq[1], until pred fails takewhile(lambda x: x<5, [1,4,6,4,1]) –> 1 4
tee() it, n it1, it2, … itn splits one iterator into n
zip_longest() (p[0], q[0]), (p[1], q[1]), … zip_longest(‘ABCD’, ‘xy’, fillvalue=’-‘) –> Ax By C- D-

groupby()函數

groupby()把疊代器中相鄰的重複元素挑出來放在一起:

for key, group in itertools.groupby('AAABBBCCAAA'):
     print(key, list(group))

A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']           

實際上挑選規則是通過函數完成的,隻要作用于函數的兩個元素傳回的值相等,這兩個元素就被認為是在一組的,而函數傳回值作為組的key。

另一個例子

# 按身高歸類
from itertools import *

def height_class(h):
    if h>180:
        return 'tall'
    elif h<160:
        return 'short'
    else:
        return 'middle'

friends = [191, 158, 159, 165, 170, 177, 181, 182, 190]

for m,n in groupby(friends,key = height_class):
    print(m)
    print(list(n))           

作業: 對于一組身高的資料(list),利用上面代碼給出的身高标準,将是以的tall, short, middle歸為一類。注意與groupby()函數的差別。

tee()函數

把一個疊代器分為n個疊代器, 傳回一個元組.預設是兩個

from itertools import *
a = "hello"
c, d, e = tee(iter(a), 3)
for i, j, k in zip(c, d, e):
    print(i, j, k)           

組合生成器

product() p, q, … [repeat=1] cartesian product, equivalent to a nested for-loop
permutations() p[, r] r-length tuples, all possible orderings, no repeated elements
combinations() p, r r-length tuples, in sorted order, no repeated elements
combinations_with_replacement() r-length tuples, in sorted order, with repeated elements
product(‘ABCD’, repeat=2) AA AB AC AD BA BB BC BD CA CB CC CD DA DB DC DD
permutations(‘ABCD’, 2) AB AC AD BA BC BD CA CB CD DA DB DC
combinations(‘ABCD’, 2) AB AC AD BC BD CD
combinations_with_replacement(‘ABCD’, 2) AA AB AC AD BB BC BD CC CD DD

copy: 淺拷貝(shallow copy), deepcopy: 深拷貝(deep copy).

  • 我們尋常意義的複制就是深複制,即将被複制對象完全再複制一遍作為獨立的新個體單獨存在。是以改變原有被複制對象不會對已經複制出來的新對象産生影響。
  • 而淺複制并不會産生一個獨立的對象單獨存在,他隻是将原有的資料塊打上一個新标簽,是以當其中一個标簽被改變的時候,資料塊就會發生變化,另一個标簽也會随之改變。這就和我們尋常意義上的複制有所不同了。
  • 對于簡單的 object,用 shallow copy 和 deep copy 沒差別
  • 複雜的 object, 如 list 中套着 list 的情況,shallow copy 中的 子list,并未從原 object 真的「獨立」出來。也就是說,如果你改變原 object 的子 list 中的一個元素,你的 copy 就會跟着一起變。這跟我們直覺上對「複制」的了解不同。

例子:

from copy import copy, deepcopy

#origin 裡邊有三個元素:1,2,[3, 4]
origin = [1, 2, [3, 4]]

# cop1為淺拷貝,cop2為深拷貝
cop1 = copy(origin)
cop2 = deepcopy(origin)

# cop1是否與cop2内容相同
print(cop1 == cop2)
# cop1是否與cop2為同一個引用
print(cop1 is cop2)

# 改變origin中嵌套清單中的元素
origin[2][0] = "hey"

# 檢視輸出
print(origin)
print(cop1)
print(cop2)

# 改變origin中嵌套清單中的元素
origin[1] = "hello"

# 檢視輸出
print(origin)
print(cop1)
print(cop2)           

輸出結果:

True
False
[1, 2, ['hey', 4]]
[1, 2, ['hey', 4]]
[1, 2, [3, 4]]
[1, 'hello', ['hey', 4]]
[1, 2, ['hey', 4]]
[1, 2, [3, 4]]
      

在Python中,一個Python檔案就是一個子產品。

子產品讓你能夠有邏輯地組織你的 Python 代碼段。

把相關的代碼配置設定到一個子產品裡能讓你的代碼更好用,更易懂。

子產品能定義函數,類和變量,子產品裡也能包含可執行的代碼。

一個簡單的子產品例子:

hello.py

def say_hello(name):
    s = 'hello, %s!'%name
    return s           

使用子產品:

  • import module
  • from module import …
import hello

print(hello.say_hello("Lee"))

from hello import say_hello

print(say_hello("Jack"))           

os子產品

os子產品包含普遍的作業系統功能。

os常用方法及屬性

os.sep 可以取代作業系統特定的路徑分隔符。windows下為 “\”

os.name字元串訓示你正在使用的平台。比如對于Windows,它是’nt’,而對于Linux/Unix使用者,它是’posix’。

os.getcwd() 函數得到目前工作目錄,即目前Python腳本工作的目錄路徑。

os.getenv() 擷取一個環境變量,如果沒有傳回none

os.putenv(key, value) 設定一個環境變量值

os.listdir(path) 傳回指定目錄下的所有檔案和目錄名。

os.remove(path) 函數用來删除一個檔案。

os.system(command) 函數用來運作shell指令。

os.linesep 字元串給出目前平台使用的行終止符。例如,Windows使用’\r\n’,Linux使用’\n’而Mac使用’\r’。

os.curdir: 傳回目前目錄(’.’)

os.chdir(dirname): 改變工作目錄到dirname

os.path常用方法:

os.path.isfile()和os.path.isdir()函數分别檢驗給出的路徑是一個檔案還是目錄。

os.path.existe()函數用來檢驗給出的路徑是否真地存在

os.path.getsize(name):獲得檔案大小,如果name是目錄傳回0L

os.path.abspath(name):獲得絕對路徑

os.path.normpath(path):規範path字元串形式

os.path.split(path) :将path分割成目錄和檔案名二進制組傳回。

os.path.splitext():分離檔案名與擴充名

os.path.join(path,name):連接配接目錄與檔案名或目錄;使用“\”連接配接

os.path.basename(path):傳回檔案名

os.path.dirname(path):傳回檔案路徑

sys子產品

sys子產品提供了一系列有關Python運作環境的變量和函數。

sys子產品的常用方法

sys.argv: 實作從終端向程式傳遞參數。

sys.exit([arg]): 程式中間的退出,arg=0為正常退出。

sys.getdefaultencoding(): 擷取系統目前編碼,一般預設為ascii。

sys.setdefaultencoding(): 設定系統預設編碼,執行dir(sys)時不會看到這個方法,在解釋器中執行不通過,可以先執行reload(sys),在執行 setdefaultencoding(‘utf8’),此時将系統預設編碼設定為utf8。(見設定系統預設編碼 )

sys.getfilesystemencoding(): 擷取檔案系統使用編碼方式,Windows下傳回’mbcs’,mac下傳回’utf-8’.

sys.path: 擷取指定子產品搜尋路徑的字元串集合,可以将寫好的子產品放在得到的某個路徑下,就可以在程式中import時正确找到。

sys.platform: 擷取目前系統平台。

sys.stdin, sys.stdout, sys.stderr: stdin , stdout , 以及stderr 變量包含與标準I/O 流對應的流對象. 如果需要更好地控制輸出,而print 不能滿足你的要求, 它們就是你所需要的. 你也可以替換它們, 這時候你就可以重定向輸出和輸入到其它裝置( device ), 或者以非标準的方式處理它們

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

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

建立generator的辦法:

  • 把一個清單生成式的[]改成()
  • yield關鍵字

将清單的[]改成()的例子:

# 清單生成式
L = [x * x for x in range(10)]
print(type(L))

# 建立生成器
g = (x * x for x in range(10))
print(type(g))

# 擷取下一個傳回值
# 當沒有更多元素時,會抛出StopIteration錯誤
print(next(g))
print(next(g))
print(next(g))

# for循環
for n in g:
    print(n)           

通過yield建立生成器

# 普通方法生成斐波拉契數列
# 前幾個斐波拉契數
def fib1(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

fib1(6)

# 通過yield建立生成器
# 注意yield的執行流程
def fib2(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

# 将生成器函數指派給變量f
f = fib2(6)
print(type(f))
for n in f:
    print(n)           

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

generator執行流程的了解:

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)

o = odd()

print(next(o))
print(next(o))
print(next(o))           

可以直接作用于for循環的資料類型有以下幾種:

  • 集合資料類型,如list、tuple、dict、set、str等;
  • generator,包括生成器和帶yield的generator function。

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

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

from collections import Iterable

# 判斷空清單是否為Iterable對象
# True
print(isinstance([], Iterable))

# 判斷空集合是否為Iterable對象
# True
print(isinstance({}, Iterable))

# 判斷字元是否為Iterable對象
# True
print(isinstance('abc', Iterable))

# 判斷生成器是否為Iterable對象
# True
print(isinstance((x for x in range(10)), Iterable))

# 判斷數字否為Iterable對象
# False
print(isinstance(100, Iterable))           

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

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

from collections import Iterator

# 判斷生成器是否為Iterator對象
# True
print(isinstance((x for x in range(10)), Iterator))

# 判斷空清單是否為Iterator對象
# False
print(isinstance([], Iterator))

# 判斷空集合是否為Iterator對象
# False
print(isinstance({}, Iterator))

# 判斷字元串是否為Iterator對象
# False
print(isinstance('abc', Iterator))           

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

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