python學習筆記


文章目錄

0、前言

本文是根據廖雪峯python教程學習總結而來。
參考《python學習手冊》,《流暢的python》以及python官方手冊等網絡資料
略過了與C和C++ 語言相同的語法部分

1、轉義方式

python的字符串可以用雙引號和單引號,作用是相同的。轉義的方式和C差不多。不同的是,如果想讓一個字符串裏面的字符都不用轉義的話,可以在字符串前面加字母r來聲明。
例如

r'\\\\t\t\t\t\'

裏面的內容都無需轉義。
或者使用3個引號來把字符串包起來,這樣也不需要轉義了,而且中間還可以換行。
例如

'''123\\\t\t\t\'''

2、字符串的佔位符

百分號佔位符

和C語言一樣,都是使用百分號佔位,但是不同的是,python裏面佔位符對應的數據也需要百分號來標誌,而且佔位符對應的數據與字符串之間不需要加逗號。
例如

print ("我叫%s,今年%d歲,工作%d年了" %('鄭德倫', 27, 2))

format格式化佔位符

還有一種新的佔位符使用{0}佔位,有點像C#的格式化字符串操作。
python官方手冊str.format部分

print('{0:,.3f} {1},{name}, {2},'.format(99999.12345, '嘎嘎',[1,2,3,4], name='123'))

輸出:

99,999.123 嘎嘎,123, [1, 2, 3, 4],

3、布爾值

True False表示真假,布爾運算是and or not,這點與C語言不同

4、if判斷

與C語言不同的是,if和else後面都加冒號,else if變成了elif

a = 10
b = 20
if a > b :
    print (a > b)
elif a == b:
    print (a == b)
else:
    print (a < b)

5、除法運算

C語言中,兩個整數相除的結果還是整數,而在python中,結果是一個浮點數。
如果想讓兩個整數相除的結果變成整數的話,需要用地板除的方式,雙斜線
例如:

10//3

結果是3

6、list

list有點像是C++ 中的vector,可以動態的添加和刪除元素。是一個有序的表。可以對一個list使用len來獲取長度。與C++ 不同,C++ 是強類型的語言,vector中只能包含相同類型的元素,而python的list中可以包含不同類型的對象,還可以包含另一個list。
list使用中括號來表示
例如:

['123', '234', '345]

通過索引訪問list時,還可以使用負數,-1就是取最後一個元素

a=['123', '234', '345']  
a[-1]

輸出345
在後面追加元素,相當於C++ 中vector的push_back,在python中是append
例如

a.append('666')

在中間插入元素,相當於C++ 的insert,在python也是insert
例如a.insert(1, ‘777’) 在索引號1的地方插入’777’
在末尾刪除元素,相當於C++ 的pop_back,在python中是pop
例如

a.pop()

刪除指定索引的元素,在C++ 中無法通過索引直接刪除vector的元素,需要使用迭代器,在python中也用pop刪除指定位置的元素
例如

a.pop(1)

刪除索引1的元素

7、tuple

tuple類似於C++ 中的tuple,和python的list也很像,只不過tuple一旦創建就不能更改裏面的內容了。
tuple可以隱式轉換爲單個變量例如:

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

輸出:

1 2 3

也可以使用封包

first, *rest = (1,2,3,4)
print(first)
print(rest)

輸出

1
[2, 3, 4]

8、輸入輸出

輸出函數和C語言類似使用

print ("hello world")

輸入函數與C和C++ 都比較不同,使用input來獲取輸入的內容
例如

name = input("input your name")

9、數字字符串轉換

在C和C++ 中數字轉字符串比較麻煩,一般在C語言中可以使用sprintf。C++ 直接使用to_string。
字符串轉數字的話,C語言可以使用atoi,C++ 可以使用stringstream。
在python中可以直接使用int() float() str()轉換
例如:

a = int('123')
b = float('12.3)
c = str(123.3)

10、循環

python中有兩種循環,一種是for循環一種是while循環。和C、C++ 中的不太一樣。沒有do while循環
for循環:類似於C++中的for (auto i : vec)這種range-based的for循環

for x in range(10):
    print(x)

while循環:和C、C++中的while循環類似

while x < 10:
    print(x)
    x += 1

python的循環中同樣可以使用continue和break來終止循環

11、字典dict

字典類似與C++ 中的unordered_map,是一種哈希表的key-value的查找結構。
使用大括號來表示:

score = {"Mike":100, "冰封飛飛":100, "Fvck":88}

判斷一個key在不在字典裏面可以使用
“冰封飛飛” in score
返回結果是True或者False
刪除一個元素使用pop方法。score.pop(“冰封飛飛”)
插入一個元素和C++一樣直接可以score[“new”] = 100
查找操作可以使用get操作,因爲如果key不存在的時候,直接使用score[“nonexist”]會報錯。
get操作還可以指定默認值,如果查找不到key的話,返回默認值

s = {}
print(s.get('a', '3'))

結果:

3

遍歷dict:

for (k, v) in score.items():
    print("%s:%d" % (k, v))

12、集合set

集合類似與C++ 中的unordered_ser,是一種哈希表的集合結構。
如果要創建set,需要提供一個list作爲輸入集合。
s = set([1, 2, 2, 3]) 初始化時,自動會去重元素。
結果是s = {1, 2, 3}
添加元素使用add方法,刪除元素使用remove方法

s.add(4)
s.remove(4)

集合類似於數學上的集合,有交集和並集的操作,
s1 & s2, s1 | s2

13、函數

函數名是一個指向函數對象的引用,下面可以直接將函數賦值一個別名

a = abs
a(-10)

而且函數名本身也是一個變量,可以賦值

abs = 10
abs(-10)

這樣執行的話,abs(-10)就會報錯了。

python包含了很多內置的函數,可以從python官方手冊查看
函數的定義使用def

def myFunc(a, b):
    return a + b

空語句可以使用pass,類似於C/C++ 中的分號
可變參數:在參數前面加一個星號,就變成了可變參數,可以傳入任意個數的參數,實際上是自動封裝成了一個tuple。

可變參數

def myFunc(*num):
    result = 0
    for i in num:
    result += i
    return result

調用時可以myFunc(1, 2, 3)這樣調用,
也可以傳入一個list或者tuple

param = [1, 2, 3]
myFunc(*param)

關鍵字參數

def func(a, b, **kw):
    print(kw)

這個函數裏面第三個參數是**kw,實際上是一個字典。
調用時可以這樣調用:func(1, 2, name=“123”, age=18)
參數傳進去時,實際上a = 1, b = 2 kw = {“name”:“123”, “age”:18}
也可以直接傳一個dict作爲第三個參數,但是前面需要加兩個星號

kw =  {"name":"123", "age":18}
func(1, 2, **kw)

關鍵字參數必須跟在位置參數後面。下面的調用方式是不合法的。

def func(a, b=1, c=2):
    print(a, b, c)
func(a=1, 2)

命名關鍵字參數:

def func(a, b, *, name, job):
    print(name)
    print(job)

在上面關鍵字參數,無法控制**kw傳入的key是什麼。使用關鍵字參數可以控制可以傳入哪些key。需要使用一個星號作爲分隔符。星號後面定義可以傳入的key的名稱。

func(1, 2, name="1", job=2)  -> OK
func(1, 2, job = 3) -> ERROR

這個星號必不可少,如果正好有一個可變參數,那就不需要額外的星號了
def func(a, b, *args, name, job):也是可以的

參數組合

定義函數的時候可以使用多重參數組合,定義的順序是,必選參數,默認參數,可變參數,命名關鍵字參數和關鍵字參數

def f1(a, b = 0, *args, **kw)
def f2(a, b = 0, *, d, **kw)

對於任意的函數都可以使用一個tuple和一個dict來組織參數調用。類似於func(*args, **kw)

函數註釋/註解

函數註釋是3.X新加入的功能,可以給函數每一個參數添加一個註釋,以及標記返回值的類型。

def func(a:'spam', b:(1,10),c:float=2.5) -> int:
    return a + b + c

註解實際上是把上面添加的內容,寫入到了函數的__annotations__方法中。
可以使用

print(func.__annotations__)

打印函數註解,結果如下

{'a': 'spam', 'b': (1, 10), 'c': <class 'float'>, 'return': <class 'int'>}

匿名函數lambda

lambda比def功能要小,lambda僅能編寫簡單的函數,連if也不能使用,也不能寫return,返回值就是表達式的結果

f = lambda x, y, z : x + y + z

裝飾器

裝飾器是一種委託的調用方式,將被裝飾的函數包裝一層,增加一些定製處理。
裝飾器的用法如下,@log是一個語法糖,相當於test = log(test),將test函數替換爲wrapper函數。
@functools.wraps(func)是系統幫忙做的一些處理,可以讓裝飾後的函數和原函數看起來一致。例如:包裝後的函數__name__還是顯示和原函數一致,不會顯示爲wrapper函數的__name__

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('<trace {0}: {1}({2})>'.format(time.ctime(), func.__name__, args, kw))
        return func(*args, **kw)
    return wrapper
@log
def test(a):    
    print('test func:{0}'.format(a))    
def main():
    test(5)
if __name__ == '__main__':
    main()

結果:

<trace Fri Oct  5 22:10:38 2018: test((5,))>
test func:5

偏函數

偏函數類似與C++ 中的bind,可以將函數的某些參數固定下來,生成一個新的函數。

import functools
int2 = functools.partial(int, base=2)
print(int2('10101'))

結果:

21

14、切片

通過切片操作可以很方便的將list,tuple,string中一段對象取出來。python沒有提供substr的函數,字符串的取子串的操作都使用切片來進行

L = list(range(10))
print("L = %s " % L)
print("L[0:3] = %s " % L[0:3])
print("L[:3]= %s " % L[:3])
print("L[-2:] = %s " % L[-2:])
print("L[-2:-1] = %s " % L[-2:-1])
print("L[:10:2] = %s " % L[:10:2])
print("L[:] = %s " %  L[:])

結果:

L = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
L[0:3] = [0, 1, 2]
L[:3]= [0, 1, 2]
L[-2:] = [8, 9]
L[-2:-1] = [8]
L[:10:2] = [0, 2, 4, 6, 8]
L[:] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

切片是一個左閉右開的區間,例如L[0:3],獲取[0, 3)區間的元素。
第二個冒號之後的數字是步長,可以相隔步長來取數
只包含一個冒號就是獲取變量本身

15、迭代

python的迭代使用for,10循環中說過一些for的內容。類似於C++ 的for(auto i : vec)這種形式的迭代。不是基於下標迭代。

迭代字典dict

d = {'a' : 1, 'b' : 2}
for key in d:
    print (key)
for value in d.values():
    print (value)
for k, v in d.items():
    print('%s : %d' % (k, v))

輸出結果:

a
b
1
2
a : 1
b : 2

對於字典,for迭代只會迭代key,如果迭代value的話需要使用d.values()作爲迭代對象。如果迭代key-value的話,需要使用d.items()作爲迭代對象
判斷一個變量是否能迭代,使用下面代碼判斷。

from collections.abc import Iterable 
d = {'a' : 1, 'b' : 2}
print(isinstance(d, Iterable))
print(isinstance(1, Iterable))

輸出

True
False

下標循環

使用enumerate可以將一個可迭代的對象轉換爲下標+對象的tuple來迭代

from collections.abc import Iterable 
d = {'a' : 1, 'b' : 2}
for i in enumerate(d.items()):
    print(i)

輸出

(0, ('a', 1))
(1, ('b', 2))

這個迭代器我用的是i一個元素,這時候i就變成了一個tuple,如果使用兩個元素的話,會將這個tuple拆分到兩個元素上面去
例如改爲:

for i, v in enumerate(d.items()):
    print('%s : %s' % (i, v))

結果就是

0 : ('a', 1)
1 : ('b', 2)

應用迭代的函數

sorted

對參數進行排序

L = [3, 2, 4, 1]
L = sorted(L)
print (L)

結果

[1, 2, 3, 4]

zip

將參數相同索引的數據組成一個tuple,如果幾個參數的數量不同,按最小的算

L = [1, 2, 3, 4]
S = ['a', 'b', 'c', 'd', 'e']
Z = zip(L, S)    
print (list(Z))

結果

[(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]

enumerate

將參數返回一個索引+數據的tuple

S = ['a', 'b', 'c', 'd', 'e']    
print (list(enumerate(S)))

結果

[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]

filter

將可迭代對象按照參數1,進行過濾。


def isInt(x):
    if isinstance(x, int):
        return True
    else:
        return False
def main():    
    S = [1, 2, 3.0, 4.1, '5', (6,)]
    print (list(filter(isInt, S)))
if __name__ == "__main__":
    main()

結果

[1, 2]

map

使用參數1中的函數,對後面的參數進行迭代運算,如果後面迭代對象數量不一致,按最小的算。

def add(x, y):
    return x + y
def main():    
    S = [1, 2, 3.0, 4.1]
    L = [2, 3, 4, 5, 6]
    print (list(map(add, S, L)))
if __name__ == "__main__":
    main()

結果

[3, 5, 7.0, 9.1]

reduce

使用參數1中的函數,對後面的參數進行累加運算,得到一個結果。

from functools import reduce
def main():    
    d = reduce(lambda x, y : x + y, range(101))
    print(d)
if __name__ == "__main__":
    main()

結果

5050

sum

對參數求和

any

對參數全部進行or運算

all

對參數全部進行and運算

max

求參數最大值

min

求參數最小值

16、列表生成式

使用range可以生成一個範圍的數據集合,但是range的返回值是Object對象,需要轉換爲其他對象纔可以使用。例如生成一個list

print(list(range(1, 11)))

結果爲:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

列表生成式

L = [ m * n for m in range(1, 10) for n in range(1, 10) if m >= n]
print(L)

結果爲:

[1, 2, 4, 3, 6, 9, 4, 8, 12, 16, 5, 10, 15, 20, 25, 6, 12, 18, 24, 30, 36,  
 7, 14, 21, 28, 35, 42, 49, 8, 16, 24, 32, 40, 48, 56, 64, 9, 18, 27, 36,   
 45, 54, 63, 72, 81]

可以用一句話來使用嵌套循環和條件判斷語句以及一個表達式來生成一個list,同樣也可以使用相同的方法來生成一個dict,一個set

字典生成式

將列表生成式的中括號換成大括號,然後將表達式換成一個key:value的形式就可以變成字典生成式了。

L = { ('%dx%d' % (m, n)) :(m * n) for m in range(1, 10) for n in range(1, 10) if m <= n}
cntLine = 1
for i, v in enumerate(L.items()):
    print(v[0], '=',  v[1], end=" ")
    if (v[1] % 9 == 0) and (v[1] / 9 == cntLine):
        print()
        cntLine += 1

結果:

1x1 = 1 1x2 = 2 1x3 = 3 1x4 = 4 1x5 = 5 1x6 = 6 1x7 = 7 1x8 = 8 1x9 = 9
2x2 = 4 2x3 = 6 2x4 = 8 2x5 = 10 2x6 = 12 2x7 = 14 2x8 = 16 2x9 = 18
3x3 = 9 3x4 = 12 3x5 = 15 3x6 = 18 3x7 = 21 3x8 = 24 3x9 = 27
4x4 = 16 4x5 = 20 4x6 = 24 4x7 = 28 4x8 = 32 4x9 = 36
5x5 = 25 5x6 = 30 5x7 = 35 5x8 = 40 5x9 = 45
6x6 = 36 6x7 = 42 6x8 = 48 6x9 = 54
7x7 = 49 7x8 = 56 7x9 = 63
8x8 = 64 8x9 = 72
9x9 = 81

集合生成式

和字典生成式沒啥區別,只是把表達式換成一個值就可以了
有列表,字典,集合生成式但是沒有tuple生成式,如果想生成一個tuple的話,需要先生成一個list然後使用tuple()轉換成一個tuple。因爲小括號這個符號被生成器生成式佔用了。

17、生成器

使用生成式來編寫生成器

將上面的生成式中括號換成小括號就變成了生成式,生成式是一種特殊的函數,使用next操作來取下一次的值,不會將所有值全部計算出來,而是用到的時候再做計算,節省內存。

g = (x * x for x in range(1, 10))
print (next(g))
print (next(g))
print (next(g))
print (next(g))

輸出:

1
4
9
16

同時,生成式也是一個可迭代的對象,可以使用for來迭代

g = (x * x for x in range(1, 10))
for i in g:
    print (i)

輸出:

1
4
9
16
25
36
49
64
81

使用yeild來編寫生成器

yeild的字典含義是產出和讓步,對於python來說,這兩個含義都成立 yeild b,這個語句會產出一個值,給next(…)的調用方,此外還會做出讓步,暫停執行生成器,讓調用方繼續工作。

def fib(num):    
    cnt, b, c = 0, 0, 1
    while cnt < num:
        yield b
        b, c = c, b + c
        cnt += 1
    print('done')

f = fib(6)
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))

結果:

0
1
1
2
3
5

yeild類似於一個不完全的return,可以保存狀態。每次到yeild時,程序會返回,然後使用next執行的話,可以從本次yeild的位置繼續執行

使用send給生成器函數傳送值

yeild是給調用者返回一個值,而send是調用者給函數發送一個值。x = yeild i這種情況下,使用send(N)之後,x就被賦值成了N。send必須在調用完至少一次next操作時,纔可調用。要確保函數的語句執行到yeild處。

def test():
    for i in range(10):
        x = yield i ** 2
        print("x={0}".format(x))
def main():
    G = test()       
    print(next(G))
    print(G.send(5)) 
    print(next(G))
    print(next(G))

結果:

0
x=5
1
x=None
4
x=None
9

生成器的return

在生成器中的return the_result語句會拋出StopIteration(the_result)異常,這樣調用方可以用異常的value屬性中獲取the_result。

18、迭代器

可以被for循環迭代的對象都叫做可迭代對象,可以使用isinstance Iterfable來判斷對象是否可以被for循環迭代

from collections.abc import Iterable
isinstance(obj, Iterable)

除了被for循環迭代,還有一些對象可以使用next迭代。可以使用isinstance Iteraotr來判斷是否可以被next迭代

from collections.abc import Iterator
isinstance(obj, Iterator)

使用iter()可以將list,dict等Iterable對象變成Iterator的

L = list(range(10))
Iter = iter(L)
print (next(Iter))
print (next(Iter))

單個迭代器和多個迭代器

有些對象支持單個迭代器,有些支持多個迭代器。單個迭代器是指,使用iter獲取該對象的迭代器時,如同單例模式一樣,多次獲取都是獲取同一個迭代器。迭代器1增加,迭代器2也會同時增加
例如range()支持多個迭代器,內置的list等也支持多個迭代器:

R = range(3)
R = range(3)
I1 = iter(R)
I2 = iter(R)
print(next(I1))
print(next(I2))
print(next(I2))

結果:

0
0
1

zip,map,filter,生成器不支持多個迭代器

Z = zip((1, 2, 3), (10, 11, 12))
I1 = iter(Z)
I2 = iter(Z)
print(next(I1))
print(next(I1))
print(next(I2))

結果:

(1, 10)
(2, 11)
(3, 12)

19、閉包

閉包的數學含義是能夠讀取其他函數變量的函數。在python中在一個函數的內部定義另外一個函數,纔可以訪問這個函數內部的變量,所以在python中閉包可以理解爲定義在函數內部的函數。

def createCounter():
    '''
    利用閉包返回一個計數器函數,每次調用它返回遞增整數:
    '''
    num = 0
    def counter():
        nonlocal num
        num += 1
        return num
    return counter

20、模塊

模塊通常是一個文件,在python中,模塊也是一個對象。模塊內的變量,函數,類都是模塊的屬性。
python文件開頭很常見的兩個註釋

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

main函數

在python中,一個模塊如果是被python直接加載的話,__name__屬性爲’main’,如果是被其他模塊加載的話,__name__就是模塊的名稱。python一般使用下面的代碼作爲main函數

if __name__=='__main__':
    test()

21、類

python3的所有類都是繼承自object基類的,也支持多重繼承。
python中的類和C++中的類有點區別,python中的類也是一種對象,類的實例也是一個對象。
python中也沒有私有屬性的概念,需要用一些其他手法來實現。在屬性名前面加雙下劃線是一種僞私有的概念,因爲他把屬性的名稱替換成了_類名__屬性名這種表示形式。

class A(object):
    def __init__(self):
        self.name = 'A'
class C(object):
    def __init__(self):
        self.color = 'Red'
        self.__private = 'C'
class B(A):
    def __init__(self):
        self.age = 50
        A.__init__(self)
        C.__init__(self)
    def __str__(self):
        result = ''
        for (k,v) in self.__dict__.items():
            result += '{0}={1}\r\n'.format(k, v)
        return result
def main():
    a = B()
    print(a)
    
if __name__ == '__main__':
    main()

結果:

age=50
name=A
color=Red
_C__private=C

__slots__

由於python是動態類型的語言,類的屬性可以使用代碼在運行過程中動態的添加。如果想要限制用戶添加的話,需要使用__slots__來實現。

class test:
    __slots__ = ['age', 'name']
    def __init__(self):
        self.test = 10

結果:

File "/media/bingfengfeifei/數據/PyCode/helloworld.py", line 37, in __init__
    self.test = 10
AttributeError: 'test' object has no attribute 'test'

實際上這種做法是把類的__dict__屬性刪除掉來實現的,動態添加和刪除屬性就是通過對類的__dict__屬性來操作實現。

property

property是將類內的屬性字段,包裝成setter,getter函數處理,可以增加一些檢查和限制。有兩種定義屬性的方式

class newprops:
    def getage(self):
        return self._age
    def setage(self, value):
        self._age = value
    def delage(self):
        del self._age    
    def __init__(self):
        self._age = 0
    age = property(getage, setage, delage, "help age") #get set del doc

class newprops2:    
    @property
    def age(self):
        return self._age
    @age.setter
    def age(self, value):
        self._age = value
    @age.deleter
    def age(self):
        del self._age        
    def __init__(self):
        self._age = 0    
def main():
    a = newprops()   
    a.age = 20 
    del(a.age)
    print(hasattr(a, 'age'))
    print(help(a))
    
if __name__ == '__main__':
    main()

結果:

False
Help on newprops in module __main__ object:

class newprops(builtins.object)
 |  Methods defined here:
 |
 |  __init__(self)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  delage(self)
 |
 |  getage(self)
 |
 |  setage(self, value)
 |
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |
 |  __dict__
 |      dictionary for instance variables (if defined)
 |
 |  __weakref__
 |      list of weak references to the object (if defined)
 |
 |  age
 |      help age

None

重載運算符

python中類的很多操作,都是重定向到一個雙下劃線開頭和結尾的函數__X__,我們重定義這個函數之後,就可以攔截一些內置的操作。

方法 重載 調用
__init__ 構造函數 對象建立時
__del__ 析構函數 對象刪除時
__add__ 運算符+ X+Y,X+=Y(如果沒有__iadd__)
__or__ 位或運算符 X|Y X|=Y
__repr__, __str__ 打印,轉換 print(X), repr(X), str(X)
__call__ 函數調用 X()
__getattr__ 點號運算 X.undefined
__setattr__ 屬性賦值語句 X.any = value
__delattr__ 屬性刪除 del X.any
__getattribute__ 屬性獲取 X.any
__getitem__ 索引運算 X[key],X[i:j]
__setitem__ 索引賦值語句 X[key]=value
__delitem__ 索引和分片刪除 del X[key]
__len__ 長度 len(X)
__bool__ 布爾測試 bool(X)
__lt__,__gt__,__le__,__ge__,__eq__,__ne__ 特定的比較 X < Y, X > Y, X <= Y等
__radd__ 右側加法 Other+X
__iadd__ 原地加法 X+=Y
__iter__,__next__ 迭代環境 I=Iter(X), next(I)
__contains__ 成員關係測試 item in X
__index__ 整數值 hex(X), bin(X)
__enter__,__exit__ 環境管理器 with obj as var:
__get__,__set__,__delete__ 屬性描述符 x.attr,x.attr=value,del x.attr
__new__ 創建 在__init__之間創建對象

枚舉類

from enum import Enum, unique
@unique
class WeekDay(Enum):
    Sun = 0 # Sun的value被設定爲0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
def main():
   print(WeekDay['Mon'])
   print(WeekDay.Sat)
   print(WeekDay(2))
   print('------------')
   for k,v in WeekDay.__members__.items():
       print(k,v,v.value)

結果:

WeekDay.Mon
WeekDay.Sat
WeekDay.Tue
------------
Sun WeekDay.Sun 0
Mon WeekDay.Mon 1
Tue WeekDay.Tue 2
Wed WeekDay.Wed 3
Thu WeekDay.Thu 4
Fri WeekDay.Fri 5
Sat WeekDay.Sat 6

元類

元類是可以生成類的類。使用元類可以在運行時動態的創建一個類。元類需要繼承type類型。

type

type可以查看一個變量的類型,type也可以動態的創建一個類。
參數1:類名
參數2:基類
參數3:方法dict

Hello = type('Hello', (object,), dict(hello=lambda self : print('hello world'))) 
a = Hello()
a.hello()

結果:

hello world

metaclass

除了使用type創建類之外,還可以使用元類metaclass來創建。metaclass實際上還是使用type去創建一個新的類。

class myMetaClass(type):
    def __new__(cls, name, bases, attrs):        
        attrs['test'] = lambda self, value: print(value)
        return type.__new__(cls, name, bases, attrs)
class myClass(metaclass=myMetaClass):
    pass

def main():
   a = myClass()
   a.test([1,2])

結果:

[1, 2]

22、異常

由於python是動態語音,python裏面任何錯誤都是通過異常來體現的,包括運行時的錯誤,“編譯錯誤“等。
python所有的異常都是從BaseException類繼承的,但是有一些是系統退出,用戶鍵盤終端等與程序運行無關的異常,也是BaseException繼承的。爲了讓用戶可以更準確的捕獲python的運行異常,通常只需要捕獲Exception異常即可。我們自定義異常時,一般也是繼承Exception類
一個異常的例子:
else分支是try沒有捕獲到異常執行,finally分支是最終都要執行的

class MyException(Exception):
    def __str__(self):
        return 'my Exception'
def main():
   
   try:
       raise MyException
   except Exception as e:
       print (e)       
   else:
       print('else')
   finally:
       print('finally')
    
if __name__ == '__main__':
    main()

結果:

my Exception
finally

異常如果沒有被捕獲,會一直向上拋出,最終python系統會打印一個錯誤的調用棧。

斷言assert

python中的斷言也是一種異常,可以看做是一種條件異常,使用assert去判斷用戶的輸入,而不要使用assert去判斷系統的錯誤,系統的錯誤由異常來處理。

assert False, 'hello'

log

python的logging模塊分爲debug,info,warning,error等幾個級別。log級別的順序也是按這個順序從低到高。如果開啓了某一個級別的log,這個級別及以上的log都會顯示。例如開啓了debug級別,所有級別的log都會顯示。如果開啓了warning級別,warning,error級別的會顯示。默認是warning級別。

import logging
logging.basicConfig(level=logging.INFO)

def main():
    logging.info('info level')
    logging.warning('warning level')
    logging.debug('debug level')
    logging.error('error level')
    
if __name__ == '__main__':
    main()

結果:

INFO:root:info level
WARNING:root:warning level
ERROR:root:error level

pdb

使用pdb可以單步調試,使用下面的命令可以使用pdb調試

python3 -m pdb helloworld.py 

也可以在代碼中,使用下面代碼加入斷點

import pdb
pdb.set_trace()

詳細的pdb使用參考python官方手冊pdb部分

單元測試

單元測試使用unittest模塊,首先編寫一個測試類,繼承自unittest.TestCase,然後編寫各種測試方法。setUp方法可以在每個測試例開始時執行,tearDown可以在每個測試例結束的時候執行。
下面是一個單元測試的例子

import unittest

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        if self.score >= 60 and self.score < 80:
            return 'B'
        elif self.score >= 80 and self.score <= 100:
            return 'A'
        elif self.score >=0 and self.score < 60:
            return 'C'
        else:
            raise ValueError
class TestStudent(unittest.TestCase):
    def setUp(self):
        print('set up')
    def tearDown(self):
        print('tear down')
    def test_80_to_100(self):
        print('start test_80_to_100')
        s1 = Student('Bart', 80)
        s2 = Student('Lisa', 100)
        self.assertEqual(s1.get_grade(), 'A')
        self.assertEqual(s2.get_grade(), 'A')
        print('end test_80_to_100')
    def test_60_to_80(self):
        print('start test_60_to_80')
        s1 = Student('Bart', 60)
        s2 = Student('Lisa', 79)
        self.assertEqual(s1.get_grade(), 'B')
        self.assertEqual(s2.get_grade(), 'B')
        print('end test_60_to_80')
    def test_0_to_60(self):
        print('start test_0_to_60')
        s1 = Student('Bart', 0)
        s2 = Student('Lisa', 59)
        self.assertEqual(s1.get_grade(), 'C')
        self.assertEqual(s2.get_grade(), 'C')
        print('end test_0_to_60')

    def test_invalid(self):
        print('start test_invalid')
        s1 = Student('Bart', -1)
        s2 = Student('Lisa', 101)
        with self.assertRaises(ValueError):
            s1.get_grade()
        with self.assertRaises(ValueError):
            s2.get_grade()
        print('end test_invalid')
    
def main():
    unittest.main()
    
if __name__ == '__main__':
    main()

結果:

set up
start test_0_to_60
end test_0_to_60
tear down
.set up
start test_60_to_80
end test_60_to_80
tear down
.set up
start test_80_to_100
end test_80_to_100
tear down
.set up
start test_invalid
end test_invalid
tear down
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

執行測試例有兩個方法:一個是使用下面的代碼執行

python3 -m unittest helloworld

還有一種是像上面的測試代碼一樣,在main函數裏面執行unittest.main()。這種方式可以像執行普通文件一樣,直接執行:

python3 helloworld.py

但是此方法在我的vscode中不能直接運行,需要從命令行輸入上面的命令去運行。

23、IO

文件

python的文件操作接口和posix接口類似,打開文本文件時,還可以通過encoding參數選擇編碼,也可以通過errors參數忽略編碼的錯誤,文件操作的示例:

def main():
    with open('test.txt', 'w', encoding='gbk') as f:
        f.write('測試gbk\r\n')
        f.write('hello world\r\n')
        f.write('哈哈哈\r\n')        
    with open('test.txt', 'r', errors='ignore') as f:
        for line in f.readlines():            
            print(line, end='')

輸出:
由於python3默認是utf8打開的,所以這裏使用gbk編碼的文件打開會出現錯誤,忽略編碼錯誤之後,中文編碼部分沒有被讀取出來

gbk
hello world

使用正確的編碼測試:

def main():
    with open('test.txt', 'w', encoding='gbk') as f:
        f.write('測試gbk\r\n')
        f.write('hello world\r\n')
        f.write('哈哈哈\r\n')        
    with open('test.txt', 'r', encoding='gbk') as f:
        for line in f.readlines():            
            print(line, end='')

輸出:

測試gbk
hello world
哈哈哈

StringIO, BytesIO

IO操作的函數不僅可以用在文件上面,也可以用在很多IO對象上面。StringIO和BytesIO可以創建IO對象。

from io import StringIO
from io import BytesIO
def main():
    f = StringIO('test\r\nhello world\r\n哈哈哈\r\n')    
    for i in f:
        print(i, end='')
    print('-------------------')
    f = StringIO()
    f.write('new\r\n')
    f.write('test\r\n')
    print(f.getvalue())
    print('-------------------')
    f = BytesIO('測試'.encode('utf-8'))
    for i in f:
        print(i)

輸出:

test
hello world
哈哈哈
-------------------
new
test

-------------------
b'\xe6\xb5\x8b\xe8\xaf\x95'

文件和目錄

python的os模塊包含了一些操作系統相關的操作。
獲取環境變量

>>> os.environ.get('PATH')
'/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/sbin:/usr/sbin'

獲取當前目錄

>>> os.path.abspath('.')
'/media/bingfengfeifei/數據/PyCode'

連接目錄,因爲不同操作系統的目錄分隔符不一樣,所以需要使用python的目錄連接來保證各個操作系統都可以生效

>>> a = os.path.join(os.path.abspath('.'),'new')
>>> os.mkdir(a)
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'new', 'test.py', 'test.txt', '__pycache__']
>>> os.rmdir(a)
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'test.py', 'test.txt', '__pycache__']
>>> 

分割目錄

>>> os.path.split('/media/bingfengfeifei/數據/PyCode/helloworld.py')
('/media/bingfengfeifei/數據/PyCode', 'helloworld.py')

分割擴展名

>>> os.path.splitext('/media/bingfengfeifei/數據/PyCode/helloworld.py')
('/media/bingfengfeifei/數據/PyCode/helloworld', '.py')

重命名

>>> os.mkdir('new')
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'new', 'test.py', 'test.txt', '__pycache__']
>>> os.rename('new', 'newnew')
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'newnew', 'test.py', 'test.txt', '__pycache__']

刪除文件

>>> os.listdir('.')
['.vscode', 'helloworld.py', 'newnew', 'test.py', 'test.txt', '__pycache__']
>>> os.remove('test.txt')
>>> os.listdir('.')
['.vscode', 'helloworld.py', 'newnew', 'test.py', '__pycache__']
>>> 

shutil模塊還補充了一些os模塊裏面沒有的系統操作。例如複製文件

>>> shutil.copyfile('helloworld.py', 'new.py')

序列化

pickle

pickle可以將內存中的數據結構,序列化成一個byte array,也可以將一個序列化好的byte array轉換成一個對象。

import pickle
def main():
    d = {'name':'蛤蛤',
         'age':-1,
         'test':[1,2,3,4]}
    with open('dump.dat', 'wb') as f:
        pickle.dump(d, f)
    with open('dump.dat', 'rb') as f:
        newDict = pickle.load(f)
    print(newDict)
    
if __name__ == '__main__':
    main()

輸出:

{'name': '蛤蛤', 'age': -1, 'test': [1, 2, 3, 4]}

dump和load方法是將序列化好的東西向IO對象操作。dumps和loads操作是向byte array操作:

import pickle
def main():
    d = {'name':'蛤蛤',
         'age':-1,
         'test':[1,2,3,4]}
    s = pickle.dumps(d)
    print(s)
    newDict = pickle.loads(s)
    print(newDict)
    
if __name__ == '__main__':
    main()

輸出:

b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x06\x00\x00\x00\xe8\x9b\xa4\xe8\x9b\xa4q\x02X\x03\x00\x00\x00ageq\x03J\xff\xff\xff\xffX\x04\x00\x00\x00testq\x04]q\x05(K\x01K\x02K\x03K\x04eu.'
{'name': '蛤蛤', 'age': -1, 'test': [1, 2, 3, 4]}

json

json模塊可以將python中的數據結構和json結構來進行轉換。同樣可以使用dumps和dump兩種方法,和pickle類似

import json
def main():
    d = {'name':'蛤蛤',
         'age':-1,
         'test':[1,2,3,4],
         'job':None,
         'alive':True}
    a = json.dumps(d)
    print(a)
    newDict = json.loads(a)
    print(newDict)

    
if __name__ == '__main__':
    main()

輸出:

{"name": "\u86e4\u86e4", "age": -1, "test": [1, 2, 3, 4], "job": null, "alive": true}
{'name': '蛤蛤', 'age': -1, 'test': [1, 2, 3, 4], 'job': None, 'alive': True}

json還可以用來序列化class,需要自定義一個dumps中的default函數,用來從class轉換爲字典,還需要自定義一個loads裏面的object_hook函數,用來從字典轉換成對象。

class JsonTest:    
    def __init__(self, name = '蛤蛤', age=-1, test=[1,2,3,4], job=None, alive=True):
        self.name = name
        self.age = age
        self.test = test
        self.job = job
        self.alive = alive
import json
def main():
    d = JsonTest()
    a = json.dumps(d,default=lambda x : x.__dict__)
    print(a)
    newDict = json.loads(a, object_hook=lambda d : JsonTest(d['name'], d['age'] + 1, d['test'], d['job'], d['alive']))
    print(newDict.__dict__)

輸出:

{"name": "\u86e4\u86e4", "age": -1, "test": [1, 2, 3, 4], "job": null, "alive": true}
{'name': '蛤蛤', 'age': 0, 'test': [1, 2, 3, 4], 'job': None, 'alive': True}

24、進程和線程

進程

詳細內容參考
廖雪峯python教程-多進程

fork

在×Nix系統下面可以使用fork來創建子進程,Windows下面沒有這個系統調用。

Process

也可以使用multiprocessing.Process來在各個平臺創建子進程,這個更爲通用

Pool

如果創建的進程數量過多可以使用multiprocessing.Pool進程池來創建多個進程。

子進程

可以使用subprocess模塊來啓動一個子進程

import subprocess

def main():
    print('$ ping www.python.org')
    r = subprocess.call(['ping', 'www.python.org', '-c', '5'])
    print('Exit code:', r)
    

結果:

$ ping www.python.org
PING dualstack.python.map.fastly.net (151.101.192.223) 56(84) bytes of data.
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=1 ttl=51 time=234 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=2 ttl=51 time=238 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=3 ttl=51 time=231 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=4 ttl=51 time=229 ms
64 bytes from 151.101.192.223 (151.101.192.223): icmp_seq=5 ttl=51 time=225 ms

--- dualstack.python.map.fastly.net ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4003ms
rtt min/avg/max/mdev = 225.711/231.863/238.158/4.302 ms
Exit code: 0

如果子進程是需要輸入內容的,還可以使用communicate()方法來輸入內容

import subprocess

def main():
    print('$ import this')
    p = subprocess.Popen(['python3'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, err = p.communicate('import this'.encode('utf-8'))
    print(output.decode('utf-8'))
    print('Exit code:', p.returncode)
    
   
    
if __name__ == '__main__':
    main()

輸出:

$ import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Exit code: 0

進程間通訊

使用Queue來通信,實現一個生產者消費者模型:

from multiprocessing import Process, Queue
def producer(q):
    print('process producer pid({0})'.format(os.getpid()))
    for i in ['A', 'B', 'C', 'D', 'E']:
        print('put value {0}'.format(i))
        q.put(i)
        time.sleep(random.random() * 3)

def consumer(q):
    print('process consumer pid({0})'.format(os.getpid()))
    while True:
        print('start value')
        value = q.get(True)
        print('get value {0}'.format(value))
def main():
    q = Queue()
    pro = Process(target=producer, args=(q,))
    con = Process(target=consumer, args=(q,))
    pro.start()
    con.start()
    pro.join()
    

    con.terminate()

輸出:

process producer pid(16750)
put value A
process consumer pid(16751)
start value
get value A
start value
put value B
get value B
start value
put value C
get value C
start value
put value D
get value D
start value
put value E
get value E
start value

線程

python中的線程使用threading模塊

import threading
import time
def loop():
    print('thread {0} is starting.'.format(threading.current_thread().name))
    for i in range(5):
        print('thread {0} loop {1}.'.format(threading.current_thread().name, i))
        time.sleep(1)
    print('thread {0} end.'.format(threading.current_thread().name))
def main():
    t = threading.Thread(target=loop,name='test')
    t2 = threading.Thread(target=loop,name='test2')
    t.start()
    t2.start()
    t.join()
    t2.join()

輸出:

thread test is starting.
thread test2 is starting.
thread test loop 0.
thread test2 loop 0.
thread test loop 1.
thread test2 loop 1.
thread test2 loop 2.
thread test loop 2.
thread test loop 3.
thread test2 loop 3.
thread test loop 4.
thread test2 loop 4.
thread test end.
thread test2 end.

python中的鎖,使用threading.Lock()創建,acquire方法來獲取鎖,release方法來釋放鎖

lock = threading.Lock()

def loop():
    lock.acquire()
    try:
        print('thread {0} is starting.'.format(threading.current_thread().name))
        for i in range(5):
            print('thread {0} loop {1}.'.format(threading.current_thread().name, i))
            time.sleep(1)
        print('thread {0} end.'.format(threading.current_thread().name))
    finally:
        lock.release()

CPython解釋器有一個GIL鎖(Global Interpreter Lock),每個python線程執行前都要先獲取GIL,然後每執行100條指令釋放鎖(python3版本改變成了每隔一段時間釋放鎖),以便於其他線程執行。所以CPython解釋器的線程並不能實現真正的併發。

線程局部變量

每個線程裏面可以共享全局變量,使用線程ThreadLocal來實現,使用threading.local()來創建ThreadLocal對象

import threading
import time

thread_local = threading.local()
def loop():
    thread_local.num = [x for x in range(10)]
    start()
def start():
    a = thread_local.num
    for i in a:
        print('thread {0} : {1}'.format(threading.current_thread().name, i))   

def main():
    t = []
    for i in range(multiprocessing.cpu_count()):
        thread = threading.Thread(target=loop)
        t.append(thread)
        thread.start()
    for i in t:
        i.join()    
    
    
if __name__ == '__main__':
    main()

25、正則表達式

貪婪模式:如果一個正則表達式可以匹配出多個結果,他會盡量多的匹配字符
非貪婪模式:儘量少的匹配字符

表達式 作用
. 匹配任意字符除了換行符
^ 匹配字符串的開頭
$ 匹配字符串的結尾
+ 表示至少一個字符
* 表示任意個字符
? 表示一個或0個字符
*?, +?, ?? 正則表達式默認是貪婪匹配,在匹配符後面加一個問號開啓非貪婪的模式
{m} 匹配確定m個字符
{m,n} 匹配m到n個字符
{m,n}? 匹配m到n個字符的非貪婪模式
\ 轉義字符
[] 匹配中括號中執行的字符,例如[a-z],[0-9A-Z]
[^5] 匹配除了5的所有字符
A|B 匹配A或者B
(…) 匹配括號裏面的表達式,並且創建一個group
\number 匹配第number個group的內容 (\d{3})A\1可以匹配’123A123’
\A 只在字符串的開頭匹配,需要配合其他表達式使用
\b 匹配單詞的開頭或者結尾,需要配合其他匹配使用
\B 匹配非單詞的開頭或者結尾與\b相對
\d 匹配數字
\D 匹配非數字
\s 匹配空白符
\S 匹配非空白符
\w 匹配字母數字下劃線
\W 匹配字母數字下劃線之外的字符
\Z 只在字符串的尾部匹配,需要配合其他表達式使用

切分字符串

s = r'123 A1    2 3fd   s a f    daA123A321Af'
    a = re.split('\s+', s)
    print(a)

輸出:

['123', 'A1', '2', '3fd', 's', 'a', 'f', 'daA123A321Af']

分組

s = r'123 A1    2 3fd   s a f    daA123A321Af'
    a = re.match(r'(\A.)(\d{2}).+([a-zA-Z]{2}).+(.\Z)', s)
    print(a.groups())

輸出:

('1', '23', 'aA', 'f')

編譯正則表達式

s1 = r'[email protected]'
    s2 = r'[email protected]'
    pattern = re.compile(r'(.+)@(.+)')
    print(pattern.match(s1).groups())
    print(pattern.match(s2).groups())

輸出:

('xszhengdelun', '126.com')
('sa614374', 'mail.ustc.edu.cn')

26、常用內建模塊

datatime

from datetime import datetime
def main():
    print(datetime.now())
    dt = datetime(2017, 3, 16, 0, 0)
    print(dt)
    print(datetime.now().timestamp())
    print(datetime.fromtimestamp(1429417200.0))
    print(datetime.strptime('2018-10-05 18:55:22', '%Y-%m-%d %H:%M:%S'))
    print(datetime.now().strftime('%a, %b %d %H:%M'))
    
if __name__ == '__main__':
    main()

輸出:

2018-10-07 21:41:19.897650
2017-03-16 00:00:00
1538919679.897687
2015-04-19 12:20:00
2018-10-05 18:55:22
Sun, Oct 07 21:41

collections

from collections import namedtuple
from collections import deque
from collections import defaultdict
from collections import OrderedDict
from collections import Counter
def main():
    Point = namedtuple('Point', ['x', 'y'])
    p = Point(1, 2)
    print('p =',p)

    deq = deque(['a', 'b', 'c'])
    deq.append('d')
    deq.appendleft('z')    
    print('deq =',deq)
    deq.pop()
    deq.popleft()
    print('deq =',deq)


    dd = defaultdict(lambda : 'N/A')
    dd['A'] = 1
    dd['B'] = 2
    print(dd['C'])

    od = OrderedDict({'a':1, 'b':2, 'c':3})    #有序dict
    print('od =',od)

    c = Counter()
    for i in 'hello world!':
        c[i] += 1
    print('c = ', c)
    
if __name__ == '__main__':
    main()

輸出:

p = Point(x=1, y=2)
deq = deque(['z', 'a', 'b', 'c', 'd'])
deq = deque(['a', 'b', 'c'])
N/A
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
c =  Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1})

base64

base64是一種二進制編碼方式,採用64個字符來表示任意的二進制數據

import base64

def main():
    a = base64.b64encode('冰封飛飛'.encode('utf-8'))
    print(a)
    print(base64.b64decode(a).decode('utf-8'))

    a = base64.urlsafe_b64encode(b'test-url-safe')
    print(a)
    print(base64.urlsafe_b64decode(a))
    
if __name__ == '__main__':
    main()

輸出:

b'5Yaw5bCB6aOe6aOe'
冰封飛飛
b'dGVzdC11cmwtc2FmZQ=='
b'test-url-safe'

struct

struct模塊可以處理字節相關的數據。詳細內容參考python官方手冊struct

字符 字節序 尺寸 對齊
< 小端序 標準
> 大端序 標準
! 網絡序 標準
@ 主機序 native native
= 主機序 標準
格式 C語言類型 Python 類型 標準尺寸
x 填充類型 no value
c char 長度爲1的bytes 1
b signed char integer 1
B unsigned char integer 1
? _Bool bool 1
h short integer 2
H unsigned short integer 2
i int integer 4
I unsigned int integer 4
l long integer 4
L unsigned long integer 4
q long long integer 8
Q unsigned long long integer 8
n ssize_t integer
N size_t integer
e float 2
f float float 4
d double float 8
s char[] bytes
p char[] bytes
P void* integer
import struct

def main():
   a =  struct.pack('=hHQ',-100, 100,100000000000)   
   print(a.hex())
    
if __name__ == '__main__':
    main()

輸出:

9cff640000e8764817000000

hashlib

import hashlib

def main():
   s = '測試MD5哈希摘要算法'
   md5 = hashlib.md5()
   md5B = hashlib.md5()
   md5.update(s.encode('utf-8'))
   for i in s:
       md5B.update(i.encode('utf-8'))
   print(md5.hexdigest())
   print(md5B.hexdigest())

   sha256 = hashlib.sha256()
   sha256B = hashlib.sha256()
   sha256.update(s.encode('utf-8'))
   for i in s:
       sha256B.update(i.encode('utf-8'))
   print(sha256.hexdigest())
   print(sha256B.hexdigest())

輸出:

725b5fca27c243ab4e61b02b0b74b6e2
725b5fca27c243ab4e61b02b0b74b6e2
bc8497b01d5d7d37bda3e8f39c291b73127242709e761fa38f0a7978f72e3fdc
bc8497b01d5d7d37bda3e8f39c291b73127242709e761fa38f0a7978f72e3fdc

hmac

import hmac

def main():
   a = hmac.new(b'test', '測試HMAC'.encode('utf-8'), digestmod='sha256')
   print(a.hexdigest())

輸出:

4599b96ad2c95e565f4f8d29c925a3eb0d3549b0b913d996b18393a02a03ea42

itertools

import itertools

def main():
    a = itertools.count(1)
    for i in a:
        print(i) # 1,2,3,4,5,6,7...無限打印

    a = itertools.cycle('ABCD')
    for i in a:
        print(i) # A B C D A B C D 循環無限打印

    a = itertools.repeat('A', 3)
    for i in a:
        print(i) # A A A 打印3次

    a = itertools.count(1)
    b = itertools.takewhile(lambda x : x <= 10, a)
    for i in b:
        print(i) #打印 1 2 3 4 5 6 7 8 9 10
    
    for i in itertools.chain('ABC', 'XYZ'):
        print(i) #打印 A B C X Y Z 連接多個迭代對象

groupby

groupby將相鄰的重複元素挑選出來放在一組

import itertools

def main():
    a = itertools.groupby('AAabBbBcCAaa', lambda c : c.upper())           
    for k, group in a:
        print(k, list(group))

輸出:

A ['A', 'A', 'a']
B ['b', 'B', 'b', 'B']
C ['c', 'C']
A ['A', 'a', 'a']

contextlib

使用with可以讓open語句在離開with時,自動調用close

with open('test.txt', 'r') as f:
    f.read()

我們可以通過重載__enter__和__exit__來實現上下文管理。

class myContext:
    def __enter__(self):
        print('enter')
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End')
def main():
    with myContext() as f:
        pass
    

輸出:

enter
End

也可以使用@contextmanager裝飾器來定義

from contextlib import contextmanager    
class myContext:
    pass
    
@contextmanager            
def context_func():
    print('begin')
    f = myContext()
    yield f
    print('end')
def main():
    with context_func() as f:
        pass    
if __name__ == '__main__':
    main()

輸出:

begin
end

還可以使用@contextmanager實現在調用前後自動執行語句

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

with tag("h1"):
    print("hello")
    print("world")

輸出:

<h1>
hello
world
</h1>

使用closing函數可以將任意對象轉換爲可以在with裏面使用的對象。closing定義如下,實際上就是在執行完之後調用close方法。如果轉換的對象沒有close方法,就GG了。

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

urllib

用於操作URL

XML

from xml.parsers.expat import ParserCreate

HTMLParser

from html.parser import HTMLParser

27、第三方模塊

Pillow

PIL是python2的圖形處理庫,Pillow是將PIL適配到python3的版本

requests

requests庫是處理URL的第三方庫,使用起來比urllib要簡單

chardet

chardet可以預測bytes的編碼,應用在TCP章節

psutil

獲取系統信息的工具,可以獲取CPU,內存,磁盤,網絡,進程

tqdm

展示命令行進度條

28、virtualenv

virtualenv可以隔離python運行環境,解決不同版本python之間的衝突問題

29、socket

TCP

python的socket使用思路和posix接口類似,只不過參數的傳入要簡單,因爲沒有類型的概念,所以記憶量要小很多,基本可以直接裸寫。linux下C語言的socket裸寫壓力還有有點大。
Server代碼:

# -*- coding: utf-8 -*-

import socket
import chardet
import threading

def handlePacket(sock, addr):
    print(sock)
    print(addr)
    while True:      
        data = sock.recv(1024)
        encoding = chardet.detect(data)['encoding']
        data = data.decode(encoding)
        print(data)
        if not data or data[:4] == 'exit':
            break
        data = 'Hello :' + data
        sock.send(data.encode(encoding))
    sock.close()
def main():    
    with socket.socket() as s:
        s.bind(('localhost', 6666))
        s.listen()
        while True:
            sock, addr = s.accept()
            handleThread = threading.Thread(target=handlePacket,args=(sock, addr))
            handleThread.start()           
    
if __name__ == '__main__':
    main()

Client代碼:

# -*- coding: utf-8 -*-
import time
import socket
def main():
    buffer = []
    with socket.socket() as s:
        data = ('MyClient', 'Test', "Python", 'exit', 'after')
        s.connect(('localhost', 6666))
        for i in data:
            s.send(i.encode('utf-8'))
            recvData = s.recv(1024)
            print(recvData.decode('utf-8'))       
    
    
if __name__ == '__main__':
    main()

Client也可以直接使用nc來連接

bingfengfeifei@bingfengfeifei-PC:nc localhost 6666

UDP

udp的代碼寫了一個客戶端和服務器ping pang報文的測試
Server:

# -*- coding: utf-8 -*-

import socket
import chardet
import threading
def main():    
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
        s.bind(('localhost', 6666))
        while True:
            data, addr = s.recvfrom(1024)
            print(data)
            s.sendto(data, addr)
    
if __name__ == '__main__':
    main()

Client:

# -*- coding: utf-8 -*-
import time
import socket
def main():
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:        
        
        s.sendto(b'ping pang test', ('localhost', 6666))
        while True:
            data, addr = s.recvfrom(1024)
            print(data,i)
            s.sendto(data, addr)

    
if __name__ == '__main__':
    main()

30、SQL

debian系mysql安裝,安裝過程中會輸入密碼,用5.7有問題,換成5.6版本了。

sudo apt install mysql-server-5.6
mysql -u root -p #root賬戶進入數據庫
create database test #創建數據庫

修改mysql配置文件vi /etc/mysql/my.cnf,添加如下內容

[client]
default-character-set = utf8

[mysqld]
default-storage-engine = INNODB
character-set-server = utf8
collation-server = utf8_general_ci

進入數據庫輸入下面內容,查看是否生效

show variables like '%char%';

pip安裝mysql驅動,兩者選其一

pip3 install mysql-connector-python --allow-external mysql-connector-python
pip3 install mysql-connector

下面創建表的時候要寫上charset=utf8,否則不支持中文

# -*- coding: utf-8 -*-
import time
import socket
import sqlite3
import os
import mysql.connector
def main():
    conn = mysql.connector.connect(user='root', password='123456', database='test', charset='utf8')
    cursor = conn.cursor()
    try:
        cursor.execute('drop table user')
    except Exception:
        pass
    try:        
        cursor.execute('create table user (id varchar(20) primary key , name varchar(20)) charset=utf8')
        cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'afbcd冰封飛飛'])
        print(cursor.rowcount)
        conn.commit()
        cursor.close()
        
        cursor = conn.cursor()
        cursor.execute('select * from user where id = %s' ,('1',))
        values = cursor.fetchall()
        print(values)
    except Exception as e:
        print(e)
    finally:
        pass
if __name__ == '__main__':
    main()

輸出:

1
[('1', 'afbcd冰封飛飛')]

SQLAlchemy

使用ORM框架的話可以嘗試SQLAlchemy,安裝方式:

pip3 install sqlalchemy

31、異步IO

協程

協程是一種輕量級的線程,在一些編程語言中語法支持,python中可以通過yeild來實現協程。C和C++目前在語法層面不支持協程,需要通過一些底層的手段hack實現, 在說協程之前需要先了解幾個概念。

併發(concurrency)和並行(parallelism)的區別:

併發強調的是在一個時間段,有多個任務可以執行,但是這裏不要求多個任務同時執行,他們可以交替執行,類似於單核處理器,操作系統的進程調度。多個任務交替執行,在用戶的眼裏是同時運行的。
並行強調的是多個任務同時執行,而不是併發這種可以交替執行的行爲。例如,在多核處理器上面,不同任務可以在不同的核上在同一時刻同時執行。

協程和線程的區別:

多個線程可能跑在同一個CPU核上,也可以跑在多個CPU核上,如果跑在一個CPU核上的話,多個線程就是由調度器來調度交替執行。如果綁在了不同的CPU核上的話,可以認爲兩個線程可以並行的執行。但是無論線程以那種形式運行,從宏觀上來看,線程是“同時運行”,而且如果沒有線程同步的話,多個線程之間的執行順序是隨機的,而且線程之間可能存在資源爭用,訪問全局資源時,通常需要加鎖。
協程也是多個任務交替執行,但是協程的控制流程,函數的切換都是自己代碼實現的,協程是多個任務協作完成的,但是邏輯上是連貫的,協程可以讓多個任務按照你的邏輯順序來執行,保證思維的連貫性,這種邏輯的順序性也讓協程不需要考慮全局資源爭用的問題,也就不需要加鎖。和線程相比,這種主動讓出型調度比線程的被動調度要高效,也比操作系統的調度代價要小得多。

協程的生產者和消費者

使用多線程的生產者消費者模式,如果沒有同步的話,生成者和消費者之間執行的次序會隨機執行,由調度決定,而協程版本,可以由調用者來決定執行順序,在這裏我們使生產者和消費者交替執行。

def consumer():
    while True:
        print('waiting data from producer...')
        data = yield        
        print('data = {0}'.format(data))
def producer(con):
    data = range(1, 20)
    con.send(None)
    for i in data:
        print('generator data {0}'.format(i))
        con.send(i)
    con.close()


def main():    
    con = consumer()
    producer(con)

輸出:

waiting data from producer...
generator data 1
data = 1
waiting data from producer...
generator data 2
data = 2
waiting data from producer...
generator data 3
data = 3
waiting data from producer...
generator data 4
data = 4
waiting data from producer...
generator data 5
data = 5
waiting data from producer...
generator data 6
data = 6
waiting data from producer...
generator data 7
data = 7
waiting data from producer...
generator data 8
data = 8
waiting data from producer...
generator data 9
data = 9
waiting data from producer...
generator data 10
data = 10
waiting data from producer...
generator data 11
data = 11
waiting data from producer...
generator data 12
data = 12
waiting data from producer...
generator data 13
data = 13
waiting data from producer...
generator data 14
data = 14
waiting data from producer...
generator data 15
data = 15
waiting data from producer...
generator data 16
data = 16
waiting data from producer...
generator data 17
data = 17
waiting data from producer...
generator data 18
data = 18
waiting data from producer...
generator data 19
data = 19
waiting data from producer...

yeild from

yeild from是新增的語法,PEP380討論加入,用法:RESULT = yield from EXPRyeild from後面跟一個生成器對象,遍歷生成器,將生成器yeild的每一個值返回給調用方。這樣可以將嵌套的協程簡化編寫。
在一個生成器函數gen中執行yeild from subgen()時,subgen會獲得控制權,把產出的值傳給gen的調用方,即調用方可以直接控制嵌套的subgen()。與此同時,gen會阻塞,直到subgen終止執行。
下面看一個yeild from的例子

def subgen():
    for i in range(5):       
        r = yield i
        print('sub gen recive send{0}'.format(r))
    return 'return mywait'

def gen():
    for i in range(2):
        print("hello world!")
        r = yield from subgen()
        print(r)
        print('hello again!')
def main():    
    try:
        g = gen()
        next(g)
        while True:            
            print(g.send('A'))
    except Exception as e:
        pass

輸出:

hello world!
sub gen recive sendA
1
sub gen recive sendA
2
sub gen recive sendA
3
sub gen recive sendA
4
sub gen recive sendA
return mywait
hello again!
hello world!
0
sub gen recive sendA
1
sub gen recive sendA
2
sub gen recive sendA
3
sub gen recive sendA
4
sub gen recive sendA
return mywait
hello again!

在這個例子中,r = yield from subgen(),執行到這句話時,函數的控制權從gen()轉移到subgen()中,並且遍歷執行。這裏yeild出來5個值0,1,2,3,4,通過yeild from直接傳遞給調用方main函數。所以在print(g.send('A'))時,就打印出來了subgen() yeild的值,而在main函數send的數值,也可以直接傳遞給subgen()裏面中去,yeild from像是一個通道打通了subgen()和main()之間的數據,gen()和subgen()之間的數據交換。r = yield from subgen()這句話的r的作用是可以接受subgen()的返回值, gen()也可以通過這樣的寫法來獲取到subgen()給自己傳遞的信息。

asyncio

以上的協程只是展示了一個在單線程內,任務切換的功能。如何實現真正的異步,還需要配合消息循環和事件來處理。asyncio是python3.4新引入的標準庫,內置了異步的IO。asyncio的編程模型是獲取模塊的主消息循環,並且把協程扔到消息循環中,實現異步IO。這樣可以在協程執行耗時操作的IO操作時,交出控制權,調度器來執行其他的協程,當IO操作執行完畢時,又可以切回執行完IO操作的協程。

@asyncio.coroutine裝飾器

asyncio包使用的協程是較爲嚴格的定義,適合asyncio API的協程在定義中必須使用yield from,而不能使用yeild。@asyncio.coroutine裝飾器應該使用到協程上面,並不是強求的,但是強烈建議這樣做。可以將協程從普通函數中凸出出來。
下面一個例子,使用協程來實現異步操作,程序的效果是模擬執行一個耗時的操作,然後控制檯繪製一個光標旋轉的操作,來表示有任務正在執行。

# -*- coding: utf-8 -*-
import asyncio
import itertools
import sys

@asyncio.coroutine
def spin(msg):
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):
        status = char + ' ' + msg
        write(status)
        flush()
        write('\x08' * len(status))
        try:
            yield from asyncio.sleep(.1)
        except asyncio.CancelledError:
            break
    write(' ' * len(status) + '\x08' * len(status))

@asyncio.coroutine
def slow_function():
    yield from asyncio.sleep(3)
    return 42

@asyncio.coroutine
def supervisor():
    spinner = asyncio.ensure_future(spin('thinking'))
    print('spinner object:', spinner)
    result = yield from slow_function()
    spinner.cancel()
    return result

def main():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(supervisor())
    loop.close()
    print('Answer:', result)
if __name__ == '__main__':
    main()

該程序的main()函數,將協程supervisor()加入主事件循環。
在supervisor()中,使用asyncio.ensure_future創建了一個協程任務(在python3.7+的版本,應該使用asyncio.create_task來創建。)然後使用yield from slow_function()來模擬執行一個耗時的操作。之後耗時操作執行完畢,關閉spinner協程。
slow_function()裏面使用asyncio.sleep(3)來模擬一個耗時3秒的操作,對於slow_function()來說,這裏會阻塞3秒的時間,而對於整個事件循環來說,這裏會將控制權交給主循環,會繼續執行其他的協程。這就是異步的sleep,如果使用time.sleep的話,整個主循環都會阻塞在這裏,其他的協程不會被調度。
spin()函數裏面循環打印字符來模擬等待。’\x08’是退格操作。
在這個例子中,slow_function()的耗時操作和spin()函數的打印是併發執行的,但是僅用到了一個線程。

async/await

python3.5使用了新的語法來表示協程,用於替代@asyncio.coroutine裝飾器和yield from語法。如果使用python3.5的協程新語法的話,只需要將
@asyncio.coroutine替換爲async
將yield from替換爲await
使用新的語法可以更加清晰的表明協程,也和其他支持協程的語言的語法更加一致。
將上面的協程代碼替換爲新的語法:

# -*- coding: utf-8 -*-
import asyncio
import itertools
import sys

async def spin(msg):
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):
        status = char + ' ' + msg
        write(status)
        flush()
        write('\x08' * len(status))
        try:
            await asyncio.sleep(.1)
        except asyncio.CancelledError:
            break
    write(' ' * len(status) + '\x08' * len(status))


async def slow_function():
    await asyncio.sleep(3)
    return 42


async def supervisor():
    spinner = asyncio.ensure_future(spin('thinking'))
    print('spinner object:', spinner)
    result = await slow_function()
    spinner.cancel()
    return result

def main():
    loop = asyncio.get_event_loop()
    result = loop.run_until_complete(supervisor())
    loop.close()
    print('Answer:', result)
if __name__ == '__main__':
    main()

aiohttp

aiohttp是一個異步的http框架,下面是使用aiohttp創建一個http server

# -*- coding: utf-8 -*-
import asyncio
from aiohttp import web

routes = web.RouteTableDef()
@routes.get('/')
async def index(request):
    #await asyncio.sleep(0.5)
    return web.Response(body=b'<h1>Index</h1>', content_type='text/html')
@routes.get('/hello/{name}')
async def hello(request):
    #await asyncio.sleep(0.5)
    text = '<h1>hello, %s!</h1>' % request.match_info['name']
    return web.Response(body=text.encode('utf-8'), content_type='text/html')
def init():
    app = web.Application()
    app.router.add_routes(routes)
    web.run_app(app, host='localhost', port='8080')
    
init()

下面是一個aiohttp作爲client的例子:
使用aiohttp,使用協程異步下載幾個文件

# -*- coding: utf-8 -*-
import time
import sys
import os

import asyncio
import aiohttp

POP20_CC = ('CN IN US ID BR PK NG BD RU JP MX PH VN ET EG DE IR TR CD FR').split()
BASE_URL = 'http://flupy.org/data/flags'

DEST_DIR = 'downloads/'

def save_flag(img, filename):
    path = os.path.join(DEST_DIR, filename)
    with open(path, 'wb') as fp:
        fp.write(img)

def get_flag(cc):
    url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
    resp = requests.get(url)
    return resp.content

def show(text):
    print(text, end=' ')
    sys.stdout.flush()

async def get_flag(cc):
    url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            image = await resp.read()
    return image

async def download_one(cc):
    image = await get_flag(cc)
    show(cc)
    save_flag(image, cc.lower() + '.gif')
    return image
def download_many(cc_list):
    loop = asyncio.get_event_loop()
    to_do = [download_one(cc) for cc in sorted(cc_list)]
    wait_coro = asyncio.wait(to_do)
    res, _ = loop.run_until_complete(wait_coro)
    loop.close()
    return len(res)
def main(download_many):
    t0 = time.time()
    count = download_many(POP20_CC)
    elapsed = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elapsed))
if __name__ == '__main__':
    main(download_many)

future

future指一種對象,表示異步執行的結果,是concurrent.futures模塊和asyncio包重要的組件,可是這兩個庫的用戶,有時卻看不到future。我們編寫的代碼可能沒有直接使用future。
從python3.4起,標準庫中有兩個名爲Future的類,concurrent.futures.Future和asyncio.Future。這兩個類的作用相同,都表示可能已經完成或者尚未完成的延遲計算。
future封裝待完成的操作,可以放入隊列,完成的狀態可以查詢,得到結果後可以獲取結果。
通常情況下,我們不應該自己創建future,而只能由併發框架(concurrent.futures或asyncio)實例化。
下面一個例子使用concurrent.futures模塊模擬下載一些內容

# -*- coding: utf-8 -*-
from concurrent import futures
import time
import random 
def download_one(cc):
    time.sleep(random.random() * 2)
    print('download {}'.format(cc))
    return cc

def download_many(cc_list):
    workers = min(20, len(cc_list))
    with futures.ThreadPoolExecutor(workers) as executor:
        res = executor.map(download_one, cc_list)
    return len(list(res))
        
def main(download_many):
    t0 = time.time()
    count = download_many(['S1', 'S2', 'S3', 'S4', 'S5', 'S6'])
    elapsed = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elapsed))
if __name__ == '__main__':
    main(download_many)

輸出:

download S1
download S4
download S3
download S5
download S2
download S6

6 flags downloaded in 1.91s

executor.map和內置map類似,不過download_one會在多個線程併發調用。和上面開頭介紹的一樣,這裏我們並沒有看到future,我們使用併發框架的時候一般不會自己去創建future。
下面的代碼是我們手動創建future對象來實現

# -*- coding: utf-8 -*-
from concurrent import futures
import time
import random 
def download_one(cc):
    time.sleep(random.random() * 2)
    print('download {}'.format(cc))
    return cc

def download_many(cc_list):
    cc_list = cc_list[:5]
    with futures.ThreadPoolExecutor(max_workers=3) as executor:
        to_do = []
        for cc in sorted(cc_list):
            future = executor.submit(download_one, cc)
            to_do.append(future)
            msg = 'Scheduled for {}: {}'
            print(msg.format(cc, future))
        results = []
        
        for future in futures.as_completed(to_do):
            res = future.result()
            msg = '{} result: {!r}'
            print(msg.format(future, res))
            results.append(res)
    return len(list(results))
        
def main(download_many):
    t0 = time.time()
    count = download_many(['S1', 'S2', 'S3', 'S4', 'S5', 'S6'])
    elapsed = time.time() - t0
    msg = '\n{} flags downloaded in {:.2f}s'
    print(msg.format(count, elapsed))
if __name__ == '__main__':
    main(download_many)

輸出:

Scheduled for S1: <Future at 0x7f1a69c009e8 state=running>
Scheduled for S2: <Future at 0x7f1a69c00b38 state=running>
Scheduled for S3: <Future at 0x7f1a69c00d68 state=running>
Scheduled for S4: <Future at 0x7f1a69c00f28 state=pending>
Scheduled for S5: <Future at 0x7f1a69c00ef0 state=pending>
download S1
<Future at 0x7f1a69c009e8 state=finished returned str> result: 'S1'
download S2
<Future at 0x7f1a69c00b38 state=finished returned str> result: 'S2'
download S4
<Future at 0x7f1a69c00f28 state=finished returned str> result: 'S4'
download S5
<Future at 0x7f1a69c00ef0 state=finished returned str> result: 'S5'
download S3
<Future at 0x7f1a69c00d68 state=finished returned str> result: 'S3'

5 flags downloaded in 1.91s

executor.submit將任務提交對執行隊列,然後返回一個future對象,這個時候對象就已經開始準備執行了。
for future in futures.as_completed(to_do):這個循環是等待執行完畢,獲取結果。
由於受GIL限制,上面的代碼都不能並行下載,但是GIL幾乎對IO密集型的處理無害,標準庫中的所有執行IO操作的函數,在等待操作系統返回結果時,都會釋放GIL。這意味着在Python語言這個層次上可以使用多線程,而IO密集型的程序可以從中受益。上面的代碼每個任務下載需要時間0-2秒,但是併發下載完5個對象,用時並沒有5倍於單次下載的時間。

ProcessPoolExecutor

如果做CPU密集的操作,可以使用ProcessPollExecutor,這個模塊將工作分配給多個Python進程處理,可以繞開GIL利用所有的CPU核心。使用方式只需要把上面的ThreadPoolExecutor替換成ProcessPoolExecutor即可

async with/async for

異步中的上下文管理器with和異步迭代器使用了新語法async with和async for。具體的簡介參考https://blog.csdn.net/tinyzhao/article/details/52684473

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章