Python

 選擇的平臺是Ubuntu16.04,發現裏面竟然已經有Python3.5.2了。不過Ubuntu應該是自帶一個python2.*的,因此要輸入Python3纔是最新版本。python

 

命令模式和交互模式算法

Python也像Lua同樣有一個交互模式,終端輸入Python便可。express

輸入輸出編程

print()輸出,好比print(「hello」,「world」)括號裏能夠能夠用逗號隔開,print遇到逗號會輸出一個空格json

input()輸入,好比name=input(),等待用戶輸入一個名字而後存到name中。能夠在括號裏寫上一些提示,好比 name = input(「input your name:」)vim

註釋windows

Python的註釋用# api

 

大小寫敏感數組

整數和浮點數在計算機內部存儲的方式是不一樣的,整數運算永遠是精確的(除法難道也是精確的?是的!),而浮點數運算則可能會有四捨五入的偏差。瀏覽器

 

用\轉義字符,常見的好比 \n 換行,\t 表示製表符等等,一樣的用 \來轉義\。若是有不少要轉義的,那就須要寫不少\,Python容許用 r ' ' 表示 ' ' 內部的字符串默認不轉義

 

若是字符串內部有不少換行,用\n寫在一行裏很差閱讀,爲了簡化,容許用 '''...''' 的格式表示多行內容,>>> 會變成 ... 如

上面是在交互模式下,若是是寫在文件中的話,就寫成這樣

 

布爾值

只有 True和False , 注意大小寫 

與或非 和lua一樣是 and or not 

 

空值 None

 

算數運算的除法 / 和 //的區別 //取整數

4/3 = 1.3333333333333333 

4//3 = 1

 

關於編碼

ASCII只能表示英文,數字,以及一些標點符號,畢竟是美國人弄的。不能表示中文,固然也不能韓文,日文等等,而後各國就有了本身的編碼,好比中國的GB2312把中文編碼進去了。那麼有那麼多語言,就會有那麼多各自不一樣的編碼,不免會衝突,多語言混合的時候就會出現亂碼。而後就有了Unicode把全部語言都統一到一套編碼裏。ASCII編碼是1個字節,而Unicode編碼一般是2個字節。若是都用Unicode,當你的文本大量是英文的時候,就形成了一倍的空間浪費。而後,又出現了把Unicode編碼轉化爲「可變長編碼」的UTF-8編碼。

如今計算機系統通用的字符編碼工做方式:在計算機內存中,統一使用Unicode編碼,當須要保存到硬盤或者須要傳輸的時候,就轉換爲UTF-8編碼。

好比:用記事本編輯的時候,從文件讀取的UTF-8字符被轉換爲Unicode字符到內存裏,編輯完成後,保存的時候再把Unicode轉換爲UTF-8保存到文件

在最新的Python 3版本中,字符串是以Unicode編碼的,也就是說,Python的字符串支持多語言

 

對於單個字符的編碼,Python提供了ord()函數獲取字符的整數表示,chr()函數把編碼轉換爲對應的字符:

好比 print(ord(‘a’))   #97

print(chr(97))  # a

 

Python對bytes類型的數據用帶b前綴的單引號或雙引號表示:b = 'ABC'.encode('ascii') # b = b'ABC'

 

len()函數顯示字符串長度。

在操做字符串時,咱們常常遇到strbytes的互相轉換。爲了不亂碼問題,應當始終堅持使用UTF-8編碼對strbytes進行轉換。

格式化輸出字符串,好比

'my name is %s , i am a %s' % ('philo' , 'student') 

特別的,當%後面只有一個參數,能夠省略括號,好比

'my name is %s' % 'philo'

 

關於代碼規範

我發如今用vim寫Python的時候,會提示一些看着奇怪的警告,查了下大概是Python代碼規範?

http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/

記錄一下實踐出來的:

1.括號(包括各類括號)內的逗號前面不要有空格,可是逗號後面要有一個

2.行內的註釋至少要在 #號前面有兩個空格

3.註釋應該以 '# '開始,也就是#後面要有一個空格,且只能有一個空格

4.dist中,鍵值對的冒號後面要有一個空格

 

list

l = ['kobe' , 'kg' , 'tracy']  這樣就表示了一個list,len()函數能夠返回list的元素個數,與lua中table不一樣的是,這裏的元素下表是從0開始的而不是1

特別的,用-1能夠索引到倒數第一個元素,而且,能夠以此類推

list裏面的元素類型能夠不同,list裏面還能夠包含list,這樣就是二維數組了,相似的,還能夠繼續有三維,四維……

l = ['kobe', 'kg', 'tim']  # 建立一個list                                        
a = len(l)   # 用len()函數獲取list長度 a = 3                                   
l.append('tracy')  # 用append()函數追加元素到list末尾                            
print(l[-1])   # tracy                                                           
l.insert(2, 'Joe')  # 插入元素到指定位置                                         
print(l[2])  # Joe                                                                  
l.pop()  # 刪除最後一個元素                                                         
print(l[-1])  # tim                                                                 
l.pop(2)  # 刪除指定位置的元素                                                      
print(l[2])  # tim                                                                  
l[2] = 'curry'  # 對指定位置賦值                                                    
print(l[2])  # curry 

 

tuple

相對於list,tuple中的元素是不可變的,不可變也就意味着數據更安全。所以,也就沒有增刪改這些方法,可是仍是能夠一樣經過索引來查詢元素。

要注意的是這裏的不可變的含義,不可變是指的「指向不變」,好比有一個tuple裏面有一個list,list裏面的元素是能夠改變的,這是容許的,由於對於tuple來講,他裏面list這個元素的指向並無變

還有一個要注意的是,當tuple裏面只有一個元素的時候,要另外加一個逗號來消除可能產生的歧義

t = ('kobe', 24, True)  # 建立一個tuple                                          
t = (1)  # 表示數字1,而不是一個tuple,這樣就產生了歧義                             
t = (1, )  # 避免歧義,加一個逗號,這樣t就表示一個tuple了                            
                                                                                    
l = ['kobe', 'tracy']                                                               
t = ('a', 'b', l)                                                                   
print(t[2][0])  # kobe                                                              
l[0] = 'tim'                                                                        
print(t[2][0])  # tim 

 

條件判斷

age = input()                                                                    
age = int(age)  # 由於input輸入的是str,str不能直接和整數比較,因此先轉換成整數  
if age >= 18:                                                                    
    print('adult')                                                               
elif age >= 6:                                                                   
    print('teenager')                                                            
else:                                                                            
    print('kid') 

Python是根據縮進來判斷條件語句時候結束了的,不像Lua要加個end

names = ['kobe', 'tracy', 'jordan']                                              
for name in names:                                                                  
    print(name)                                                                     
                                                                                    
sum = 0                                                                             
# range(101)生成一個從0開始小於101的整數list                                        
for x in range(101):                                                                
    sum = sum + x                                                                   
print(sum)  # 5050                                                                  
                                                                                    
sum = 0                                                                             
n = 1                                                                               
while n <= 100:                                                                     
    sum = sum + n                                                                   
    n = n + 1                                                                       
print(sum)  # 5050 

 

dist

就是鍵值對存儲,至關於Java中的map,用花括號{}和冒號來表示,經過方括號[]來索引

和list相比:

dist查找插入的時間很快,不會隨着key增多而變慢,須要佔用大量內存,浪費空間。(用空間換時間)

list查找和插入的時間隨着元素增多而增多,佔用空間小。    ####有點像是在比較Java中List和Map的區別

正確的使用dist要記住一條,key必定要是不能改變的,在Python中字符串和整數都是不能改變的,可是list不是,所以list是不能做爲key的

d = {'kobe': 24, 'tim': 21, 'arenas': 0}                                         
print(d['kobe'])  # 24                                                           
d['kobe'] = 8                                                                       
print(d['kobe'])  # 8                                                               
if 'tim' in d:  # 經過in來判斷dict裏面時候有該key,返回布爾值                       
    print('tim in d') 

 

set

set是一組key的集合,但沒有value,set中沒有重複元素,建立一個set要提供一個list做爲輸入集合。

和Java中set的概念大概差很少?無序不重複

經過add(key)添加元素,添加了重複元素,也只會有一個存在。remove(key)刪除元素

& 來執行兩個set的交, | 來執行兩個set的並

s = set([1, 2, 3])                                                               
print(s)  # {1, 2, 3}                                                            
s.add(99)                                                                        
print(s)  # {99, 1, 2, 3}                                                        
s.remove(2)                                                                      
print(s)  # {99, 1, 3}                                                              
s1 = set([1, 2, 3])                                                                 
s2 = set([2, 5, 1])                                                                 
print(s1 & s2)  # {1, 2}                                                            
print(s1 | s2)  # {1, 2, 3, 5}                                                      
# 不能有list這種可變的元素,由於可變,因此沒法計算哈希值                             
# s = set([1, 2, 4, [1, 2]])  # TypeError: unhashable type: 'list'

 

關於字符串是不可變的,見下面的代碼

a = 'abc'                                                                           
a.replace('a', 'A')                                                                 
print(a)  # abc,a指向的內容沒有被改變,即字符串是不可變的                          
b = a.replace('a', 'A')                                                             
print(b)  # Abc    

 

函數

#  定義一個函數
def my_abs(x):
    if x>0:
        return x
    else:
        return -x

上述代碼寫在fun.py裏面,那麼在交互模式,能夠用 from fun import my_abs 來導入這個函數,而後就能夠直接使用了

 

空函數

當須要一個什麼事都不作的函數,能夠用pass語句

有一種這樣的情景,當還沒想好函數裏面寫什麼的時候,能夠用個空函數,讓代碼先跑起來,pass還能夠用在其餘語句,如if語句裏

 

參數檢查

上面的my_abs是沒有參數檢查的,能夠用內置函數isinstance()來檢查。這樣在調用my_abs('a')的時候就會拋出一個錯誤

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x>0:
        return x
    else:
        return -x

 

多個返回值

def mul_ret():
    return 22,33

a, b = mul_ret()

print(a, b)  # 22 33
def mul_ret():
    return 22,33

a = mul_ret()

print(a)  # (22, 33)

對比這兩個執行結果能夠發現,其實Python並非返回了多個值,用一個值a也一樣能接受到,由於Python是返回的一個tuple,在語法上,返回一個tuple能夠省略括號,按位置賦給對應值

 

默認參數

def default_para(x, y = 3)
    pass

簡化函數調用,當y=3時,就只要傳入參數x。

還有就是在以後增長了函數的參數的時候,早期版本的函數調用不就不能用了,由於函數參數個數不匹配了,這個時候就能夠用默認參數,這樣就不用去修改之前的函數調用

注意有多個默認參數的狀況,好比,fun(a, b, c = 1, d = 2)。當d默認,c不默認的時候,能夠這樣調用,好比fun(a, b, 4 ), 這個好理解

當d默認,c不默認的時候,要怎麼調用?指定特定參數進行賦值,好比,fun(a,b,d = 9)

 

特別注意一個問題,當有一個這樣的函數時候,正常使用感受也還好,結果也很合理,可是若是屢次用默認參數調用該函數就會出問題。

緣由是,Python函數在定義的時候,默認參數L的值就被計算出來了,即[],由於默認參數L也是一個變量,它指向對象[],每次調用該函數,若是改變了L的內容,則下次調用時,默認參數的內容就變了,再也不是函數定義時的[]了。

因此,定義默認參數要牢記一點:默認參數必須指向不變對象!

改爲這樣,就調用多少次都沒有問題了

 

可變參數

在不使用可變參數的時候,平方和函數要寫成這樣。經過傳入一個list或者一個tuple來計算

用可變參數的時候,寫成這樣,在調用的時候,不用再套一個list或者tuple。在函數調用的時候會自動組裝成一個tuple

若是數據已是存放到一個list或者tuple裏面了,能夠這樣來調用

 

關鍵字參數

可變參數容許你傳入0個或任意個參數,這些可變參數在函數調用時自動組裝爲一個tuple

而關鍵字參數容許你傳入0個或任意個含參數名的參數,這些關鍵字參數在函數內部自動組裝爲一個dict

 

**extra表示把extra這個dict的全部key-value用關鍵字參數傳入到函數的**kw參數,kw將得到一個dict,注意kw得到的dict是extra的一份拷貝,對kw的改動不會影響到函數外的extra。 

 

命名關鍵字參數

若是要限制關鍵字參數的名字,就能夠用命名關鍵字參數,例如,只接收city和job做爲關鍵字參數。命名關鍵字參數須要一個特殊分隔符**後面的參數被視爲命名關鍵字參數

 

總結

*args是可變參數,args接收的是一個tuple;

**kw是關鍵字參數,kw接收的是一個dict。

使用*args和**kw是Python的習慣寫法,固然也能夠用其餘參數名,但最好使用習慣用法。

 

遞歸函數

見到一個頗有趣題目,就是漢諾塔的問題。這個問題用遞歸來作真是簡單優雅,特此一提。傳說某菊苣要手下搬運漢諾塔的盤子,一個64個盤子,三根柱子。問他要搬多少年?

答案是2^64 - 1,什麼概念?不算不知道,一算嚇一跳,若是他走的每一步都是對的的話,一秒走一步,他要走5849億萬年,這個數仍是忽略了億後面的數字的。。。。

這位菊苣太兇殘了,臥槽。。。。。有點像那個國王下象棋的故事

 

高級特性

切片

l = ['kobe', 'tracy', 'tim', 'lakers', 'spurs']                                  
print(l[1:3])  # ['tracy', 'tim']表示取從索引1開始取,到索引3爲止,但不包括索引3 
print(l[:3])  # ['kobe', 'tracy', 'tim'] 若是索引從0開始,那麼0是能夠省略不寫的  
print(l[-2:])  # ['lakers', 'spurs']倒數切片,從索引爲-2開始取,也就是倒數第二個元素開始取
print(l[-2:-1])  # ['lakers'] 從倒數第二個開始取,到倒數第一個爲止,但不包括倒數第一個,恩,就這樣理解
l = list(range(100))  # 生成一個0到99的list                                      
print(l[:10:2])  # [0, 2, 4, 6, 8] 有點像matlab的for循環?從0開始,到10結束(不包括10),以2爲步進長度
print(l[:10])  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 若是不寫步進長度,默認爲1        
print(l[::10])  # [0, 10, 20, 30, 40, 50, 60, 70, 80, 90] 從頭到爲,以10爲步進長度
                                                                                 
# tuple也是一種list,跟list惟一區別在於元素不可變,所以也是能夠用這種切片方式獲取元素的
t = ('a', 'b', 'c', 'd', 'e')                                                       
print(t[0:3])  # ('a', 'b', 'c')                                                    
                                                                                    
# 字符串 也能夠看作是一個list,每一個字符就是一個元素,所以也能夠用切片操做           
print('abcdefghijk'[2:7])  # cdefg 

迭代

python用for ... in ...來迭代,對比於其餘語言好比Java,這種迭代能夠實用不少場景,好比list,tuple,以及dict和字符串

 

d = {'kobe': 24, 'tracy': 1, 'kg': 21}                                           
# 迭代key                                                                        
for key in d:                                                                    
    print(key)                                                                   
'''                                                                              
kg                                                                               
kobe                                                                             
tracy                                                                            
由於dict不是順序存儲,因此每次打印結果順序可能不同                             
'''                                                                              
                                                                                 
# 迭代value                                                                      
for value in d.values():                                                         
    print(value)                                                                 
                                                                                 
# 迭代key和value                                                                 
for k, v in d.items():                                                           
    print(k, v)                                                                  
                                                                                 
# 迭代字符串                                                                     
s = 'ABCDEFG'                                                                    
for ch in s:                                                                     
    print(ch)  # 迭代每個字符打印輸出

# 迭代list的索引和對應元素
l = ['a', 'b', 'c', 'd']
for k, v in enumerate(l):
print(k, v)

'''
0 a
1 b
2 c
3 d
'''

 

 

只要一個對象是可迭代的就能夠用for,那要如何判斷時候可迭代?用collections模塊的Iterable類型判斷

from collections import Iterable                                                 
a = isinstance('abc', Iterable)                                                     
print(a)  # True 字符串是能夠迭代的                                                 
a = isinstance([1, 2, 3], Iterable)                                                 
print(a)  # True                                                                    
a = isinstance(123, Iterable)                                                       
print(a)  # False 整數是不可迭代的

 列表生成式

多寫幾種熟悉下這種寫法,主要仍是讓代碼更簡潔點

L = []
for x in range(1, 11):
    L.append(x*x)  # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# 上述代碼一樣的能夠這樣寫,結果是同樣的
L = [x*x for x in range(1, 11)]

# 裏面還能夠加條件判斷
L = [x*x for x in range(1, 11) if x % 2 == 0]  # [4, 16, 36, 64, 100] 

# 套用兩層循環
L = [m + n for m in 'abc' for n in 'ABC']  # ['aA', 'aB', 'aC', 'bA', 'bB', 'bC', 'cA', 'cB', 'cC']

# for循環同時使用多個變量
d = {'kobe':'Lakers', 'tim duncan':'spurs', 'vince carter':'raptors'}
L = [k + ' in ' + v for k, v in d.items()]  # ['kobe in Lakers', 'tim duncan in spurs', 'vince carter in raptors'] 

L = ['Hello', 'World', 18, 'Apple', None]
L = [s.lower() for s in L if isinstance(s, str)]
print(L)

 生成器

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

跟以前的列表生成式相比,區別就是[] 換成了 (),g就是一個生成器,生成器只保存算法,而不會像列表生成器同樣生成一個完整的列表

優勢在於若是要生成一個大量數據的(好比100萬)的列表,而常常用到的是前面幾個,那麼後面的就會一直佔用空間

生成器只保存算法,每次經過next()來迭代生成下一個元素。不過使用next仍是太傻了,生成器是能夠迭代的,因此能夠用for

生成器的建立方式有兩種:

1.就是上面寫的那種,列表生成式的[]換成()

2.含有yield的generator function

g = (x*x for x in range(1, 11))
for x in g:
    print(x)  # 這樣就能打印出全部元素

yield

斐波拉契數列用函數寫成這樣

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

fib(6)
'''
1
1
2
3
5
8
'''

把上面的變成generator,只要把print(b)改成 yield b就能夠了。若是一個函數定義中包含yield關鍵字,那麼這個函數就再也不是一個普通函數,而是一個generator。

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

generator和普通函數執行流程的區別:在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行,好比:

>>> f = fib(6)                                                                                                          

>>> next(f)                                                                                                            

1                                                                                                                  

>>> next(f)                                                                                                            

1                                                                                                                  

>>> next(f)                                                                                                            

2                                                                                                                      

>>> next(f)                                                                                                            

3                                                                                                                      

>>> next(f)                                                                                                            

5                                                                                                                      

>>> next(f)                                                                                                            

8                                                                                                                      

>>> next(f)                                                                                                            

Traceback (most recent call last):                                                                                        

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

StopIteration: done 

做業:寫一個generator作楊輝三角

n = 0
for t in triangles():
    print(t)
    n = n + 1
    if n == 10:
        break

# 期待輸出:
# [1]
# [1, 1]
# [1, 2, 1]
# [1, 3, 3, 1]
# [1, 4, 6, 4, 1]
# [1, 5, 10, 10, 5, 1]
# [1, 6, 15, 20, 15, 6, 1]
# [1, 7, 21, 35, 35, 21, 7, 1]
# [1, 8, 28, 56, 70, 56, 28, 8, 1]
# [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

記錄一下我寫的,沒有網上那麼簡潔,可是也仍是好理解。思路是第一行和第二行單獨考慮,第三行及之後頭尾先設置好,中間的再作計算獲得便可

def triangles():
    pre = cur = []
    n = 1
    while True:
        if n == 1:
            cur = [1]
        elif n == 2:
            cur = [1, 1]
        else:
            cur = [1 for x in range(n)]
            for x in range(1, n-1):
                cur[x] = pre[x] + pre[x-1]
        pre = cur
        n = n + 1
        yield cur

迭代器

能夠做用於for循環的數據類型有兩類:

1.集合數據類型,好比list,tuple,dict,set,str

2.generator

這些能夠直接做用於for循環的對象稱爲可迭代對象:Iterable

可使用isinstance()判斷一個對象是不是可迭代對象,好比

from collections import Iterable  # 須要導包
a = isinstance([], Iterable)  # True
a = isinstance((), Iterable)  # True
a = isinstance('abc', Iterable)  # True
a = isinstance(123, Iterable)  # Falses
a = isinstance((x*2 for x in range(10)), Iterable)  # True
print(a)

其中,generator不只能夠用於for循環,還能夠被next()不斷調用返回下一個值,知道最後拋出StopIteration錯誤表示沒法繼續返回下一個值

能夠被next()調用並不斷返回下一個值的對象稱爲迭代器:Iterator

一樣的,能夠用isinstance()方法判斷是不是迭代器對象

 

from collections import Iterator  # 須要導包
a = isinstance([], Iterator)  # False
a = isinstance((), Iterator)  # False
a = isinstance('abc', Iterator)  # False
a = isinstance(123, Iterator)  # Falses
a = isinstance((x*2 for x in range(10)), Iterator)  # True
print(a)

 

可見,list,tuple,str這些雖然可迭代(iterable)但不是迭代器(iterator)

爲何?

由於Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。

Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的

 

可使用iter()將list,tuple,str這些iterable變成iterator

from collections import Iterator  # 須要導包
a = isinstance(iter([]), Iterator)  # True
a = isinstance(iter(()), Iterator)  # True
a = isinstance(iter('abc'), Iterator)  # True
print(a)

python的for循環的本質是經過調用next()來實現的

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

徹底等價於

it = iter([1, 2, 3, 4])
while True:
    try:
        x = next(it)
    except StopIteration:
        break

 

高階函數

Higher-order function,一個函數能夠接收另外一個函數做爲參數,這種函數就稱之爲高階函數。這個概念好像跟Lua中的高階函數是同樣的?

Google的MapReduce好像很厲害,然而我並不知道是什麼東西。python內建了map()和reduce()函數。

map()

map()函數接受兩個參數,一個是函數,一個是Iterable,map將傳入的函數依次做用到序列的每一個元素,並把結果做爲新的Iterator返回。

爲了理解上面這個話,複習下,

什麼是Iterable?可直接用於for循環的對象,稱做可迭代對象。有哪些呢?集合數據類型(list,tuple,str等)和generator

什麼是Iterator?generator不只能夠用於for循環,還能夠被next()不斷調用返回下一個值,這樣的對象稱做Iterator

因此,map的第二個參數是放哪些東西並返回什麼東西就知道了。

def f(x):
    return x*x

r = map(f, [1, 2, 3, 4, 5])
print(list(r))  # [1, 4, 9, 16, 25]

上述代碼中,map將傳入的函數f(x)=x2依次做用到序列[1, 2, 3, 4, 5]的每一個元素。返回的r是一個Iterator。等價於下面的代碼。只是用map顯得更簡潔點。

l = []
for x in [1, 2, 3, 4, 5]:
    l.append(f(x))

reduce()

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

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

好比,求一個list的元素的和

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

r = reduce(add, [1, 2, 3, 4, 5])
print(r)

 也能夠寫成lambda函數,這樣就能夠省去add函數了。感受若是是同樣簡單的操做,用這個仍是比較方便的。由於直接就獲得了xy兩個參數進行操做。

r = reduce(lambda x, y:x+y , [1, 2, 3, 4, 5])

reduce中的那個函數必須接受兩個參數,那若是傳入的序列只有一個參數要怎麼辦?

其實reduce的格式是這樣的 reduce( func, seq[, init] ) ,裏面的init是可選的,若是寫了init,那麼init就和序列中的第一個元素做爲func的參數傳入,可是隻參加一次,之後的迭代不會再用到init了。

 

做業

1.利用map()函數,把用戶輸入的不規範的英文名字,變爲首字母大寫,其餘小寫的規範名字。輸入:['adam', 'LISA', 'barT'],輸出:['Adam', 'Lisa', 'Bart']

# 測試:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)

 

def normalize(name):
    c = ''  # 由於字符串不可變,構建一個新的字符串返回
    for index, ch in enumerate(name):  # 用enumerate能夠獲得元素和對應的索引
        if index == 0:
            c = c + ch.upper()  # 第一個元素大寫
        else:
            c = c + ch.lower()  # 其餘的小寫
    return c

查了下,有個叫capitalize()的函數專門幹這個的。一句就夠。

def normalize(name):
    return name.capitalize()

 

filter

python內建filter()函數用於過濾序列。和map()相似,filter()接收一個函數一個序列。和map()不一樣的是,filter()把傳入的函數依次做用於每一個元素,而後根據返回值是True仍是False決定保留仍是丟棄該元素。返回True就留下,False丟棄。最終返回的是一個Iterator。例如

 

def is_odd(x):  # 只要奇數不要偶數
    return x % 2 == 1

l = list(filter(is_odd, range(1, 10)))  # [1, 3, 5, 7, 9]

 

 

做業

用filter()過濾回數,回數就是從左到右跟從右到左年同樣的數,

def is_palindrome(n):
    return str(n) == str(n)[::-1]  # 這裏[::-1]表示的是字符串從頭至尾,步進長度-1,也就是逆序

output = filter(is_palindrome, range(1, 1000))
print(list(output))

 

排序 sorted

l = [99, -2, 0, 2, 18, 10]
print(sorted(l))  # [-2, 0, 2, 10, 18, 99]
# sorted()函數也是一個高階函數,它還能夠接收一個key函數來實現自定義的排序
# 將l的每個元素做用到函數abs上,而後再進行排序
print(sorted(l, key = abs))  # [0, -2, 2, 10, 18, 99]
l = ['Kobe', 'allen', 'Tracy', 'tim', 'kg']
print(sorted(l))  # ['Kobe', 'Tracy', 'allen', 'kg', 'tim'] 默認的字母排序是'Z' > 'z'的, 也就是大寫會排在前面
print(sorted(l, key = str.lower))  # ['allen', 'kg', 'Kobe', 'tim', 'Tracy']  這樣就實現的不分大小寫來進行排序
print(sorted(l, key = str.lower, reverse = True))  # ['Tracy', 'tim', 'Kobe', 'kg', 'allen'] 逆序輸出,注意True是大寫的T

做業

假設咱們用一組tuple表示學生名字和成績:

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] 

請用sorted()對上述列表分別按名字排序,而後再按成績從高到低排序::

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return str.lower(t[0])

def by_score(t):
    return t[1]
L2 = sorted(L, key=by_name)
print(L2)
L2 = sorted(L, key=by_score, reverse=True)
print(L2)

 

返回函數

高階函數除了能夠接受函數做爲參數外還能夠把函數做爲返回值返回

def cala_sum(*numbers):  # 求和函數寫成這樣
    a = 0
    for i in numbers:
        a = a + i
    return a

print(cala_sum(1, 2, 3, 4, 5))  # 15

# 若是不要求馬上執行
def lazy_sum(*numbers):
    def cala_sum():  
        # 注意這裏不能*numbers做爲形參了,我理解的是這樣就覆蓋了lazy_sum的形參,由於同名
        # 可是卻不會由於同名而把參數傳下來,除非實際調用cala_sum(args),函數體裏面才能用到傳進來的args
        a = 0
        for i in numbers:
            a = a + i
        return a
    return cala_sum

f = lazy_sum(1, 2, 3, 4, 5)
print(f())

 

每次執行lazy_sum都會返回一個函數,可是每次返回的函數是不同的,也就是否是指向一個地址,大概能夠這樣理解。兩個函數的調用結果互相不影響

f1 = lazy_sum(1, 2, 3, 4, 5)
f2 = lazy_sum(1, 2, 3, 4, 5)
print(f1 == f2) # false

 

再看一個例子

def f():
    l = []
    for i in range(1, 4):
        def r():
            return i*i
        l.append(r)
    return l

f1, f2, f3 = f()

print(f1())  # 9
print(f2())  # 9
print(f3())     # 9

這裏不是輸出1,4,9 爲何?由於for循環每次循環都建立一個函數,而後將這個函數加到一個list裏面,而這個函數並無被執行,我這樣理解,由於這個函數並無被調用,而只是定義好了,也就是說裏面的i*i並不會執行

等到顯示的調用該函數的時候,for循環已經結束,而且i=3。

這種搞法叫作Closure,閉包,Lua裏面也有

 

匿名函數

好比以前的map提到的lambda,以下代碼所示,lambda中表示的就是f(x) = x2, 這就是一個匿名函數。限制是只能有一個表達式,不用寫return,返回值就是表達式的結果,匿名函數也能夠當作一個函數的返回值來返回

l = list(map(lambda x: x*x , [1, 2, 3, 4, 5]))
print(l)  # [1, 4, 9, 16, 25]

 

裝飾器

如今要加強一個函數的功能,好比,在函數調用前執行begin call,在調用後執行end call,能夠這樣幹

 

def hello():
    print('hello world')

def deco(func):
    print('begin call')
    func()
    print('end call')
    return func

hello = deco(hello)

 

這樣雖然會輸出想要的,hello的定義沒變。

繼續用語法糖@來實現。在定義hello的時候加上@deco,其實就表示hello = deco(hello),會自動執行deco方法,而且hello沒有改變,再單獨調用 hello()的時候只會輸出 hello world,不會有begin call這些

def deco(func):
    print('begin call')
    func()
    print('end call')
    return func

@deco
def hello():
    print('hello world')

使用內嵌包函數,使得以後每次調用也會出現begin call 和 end call。這個時候hello就指向deco裏面的wrapper函數了,能夠打印hello.__name__來看函數的名字,結果是wrapper而不是hello,因此以後每次調用hello都會有裝飾結果

def deco(func):
    def wrapper():
        print('begin call')
        func()
        print('end call')
        # 這裏須要返回原函數func的返回值
    return wrapper

@deco
def hello():
    print('hello world')

 裝飾帶參數的函數

def deco(func):
    def wrapper(a, b):
        print('begin call')
        res = func(a, b)
        print('end call')
        return res 
    return wrapper

@deco
def add(a, b):
    return a + b

r = add(1, 2)  # 3

 當裝飾不肯定參數數量時

def deco(func):
    def wrapper(*args, **kw):
        print('begin call')
        print('call', func.__name__)
        res = func(*args, **kw)
        print('end call')
        return res 
    return wrapper

@deco
def add(a, b):
    return a + b

@deco
def addMore(a, b, c):
    return a + b + c

r = add(1, 2)  # 3
print(r)

r = addMore(1, 2, 3)  # 6
print(r)

 

偏函數

經過functools.partial來固定某個函數的某個或者多個參數,產生一個新的函數。建立偏函數時,實際上能夠接收函數對象、*args**kw這3個參數
import functools
def hello(s = 'world', st = '!'):
    print('hello', s, st)

h = functools.partial(hello, s = 'kobe', st = ', I see you!')

h()  # hello kobe , I see you!

kw = {'s':'tracy'}
f = functools.partial(hello, **kw)
f()  # hello tracy!

 

模塊

有點相似lua的模塊,不一樣的是,Python的每個包目錄下面都會有一個__init__.py的文件,這個文件是必須存在的,不然,Python就把這個目錄當成普通目錄,而不是一個包。__init__.py能夠是空文件,也能夠有Python代碼,由於__init__.py自己就是一個模塊,而它的模塊名就是mycompany

模塊頭幾行的標準註釋,固然不寫也沒有關係

第1行註釋可讓這個hello.py文件直接在Unix/Linux/Mac上運行,第2行註釋表示.py文件自己使用標準UTF-8編碼

 

第4行是一個字符串,表示模塊的文檔註釋,任何模塊代碼的第一個字符串都被視爲模塊的文檔註釋;

 

第6行使用__author__變量把做者寫進去,這樣當你公開源代碼後別人就能夠瞻仰你的大名;

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

' a test module '

__author__ = 'Michael Liao'

__**和_**的命名默認是private,實際上是能夠引用的,只是習慣和一種約定,由於Python無法實現private私有變量,Lua也相似。

 

面向對象

建立類和對象

class Student(object):  # 父類寫在括號裏面
    def __init__(self, name, score):  # 構造函數,__init__方法的第一個參數永遠是self,表示建立的實例自己。
        self.name = name
        self.score = score

    def print_score(self):  # 成員函數的第一個參數也是self
        print(self.score)

bart = Student('kobe', 24)  # 不用傳self,這個有點像lua用冒號來定義函數的意思了
bart.print_score()  # 調用不用傳self

一個類的兩個不一樣的對象能夠有不一樣的屬性,好比建立了對象後,本身定義一個age屬性,bart.age = 33 。那麼這個屬性只會在bart這個對象裏。

訪問限制

用雙下劃線開頭的成員變量,好比__name是不能直接訪問的。雙下劃線開頭就意味着,這個是private。若是用bart.__name = 'aaaa'來賦值,會成功,可是這個__name與類裏面定義的__name不是同一個變量了!!!

可是,注意__name__這種變量是特殊變量,並非私有變量,因此是能夠訪問的。

 

有些時候,你會看到以一個下劃線開頭的實例變量名,好比_name,這樣的實例變量外部是能夠訪問的,可是,按照約定俗成的規定,當你看到這樣的變量時,意思就是,「雖然我能夠被訪問,可是,請把我視爲私有變量,不要隨意訪問」。

 

雙下劃線開頭的實例變量是否是必定不能從外部訪問呢?其實也不是。不能直接訪問__name是由於Python解釋器對外把__name變量改爲了_Student__name,因此,仍然能夠經過_Student__name來訪問__name變量

因此!一切靠自覺!養成好的習慣!

 

繼承

 

class Animal(object):
    def run(self):
        print('Animal is running')
    
class Dog(Animal):  #括號裏寫上父類,表示繼承自Animal
    pass  # 能夠複寫run方法

d = Dog()  # 繼承了父類的run方法
d.run()  # Animal is running

 

靜態語言 vs 動態語言

對於靜態語言(例如Java)來講,若是須要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,不然,將沒法調用run()方法。

對於Python這樣的動態語言來講,則不必定須要傳入Animal類型。咱們只須要保證傳入的對象有一個run()方法就能夠了(竟然還有這種事!)

獲取對象信息

https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431866385235335917b66049448ab14a499afd5b24db000

 面相對象高級編程

使用 __slots__

Python是能夠動態設置對象的屬性的。好比類Student裏面沒有name這個屬性,那麼s1.name = "kobe"後,s1對象是有name這個屬性的,可是s2就沒有了。這個毫無疑問。可是Python是能夠直接對類動態設置屬性的,好比Student.name = "kobe",那麼他的對象就都有這個屬性了。

使用 __slots__ 能夠限制這種動態設置。好比

class Student(object):
    __slots__ = ("name", "age")
    
s1 = Student()
s1.name = "Kobe"
s1.age = 24
s1.number = 8

Student類中用 __slots__ 限制了只能綁定name和age這兩個屬性,因此在s1.number = 8 企圖綁定number的時候會報錯。測試了下,這個只是針對動態綁定這種狀況,事先在類裏面定義其餘屬性是沒有關係的。

並且這種限定不會延伸到子類。好比

class GraduateStudent(Student):
    pass

g = GraduateStudent()
g.number = 8

子類能夠綁定number這個屬性

 

使用 @property

簡單來講@property能夠給屬性加上檢查。就是當使用 s.score = 59 這種方式給屬性賦值時,能夠檢查賦值的值是否符合規範。

 

class Student(object):
    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be int')
        if value < 0 or value > 100:
            raise ValueError('score must be 0~100')
        self._score = value

s = Student()
s.score = 59
print(s.score)  # 59

 

能夠這樣理解,@property把一個getter()方法變成了屬性,@property自己又建立了另外一個裝飾器@score.setter,負責把一個setter方法變成屬性賦值。

只讀屬性。只設置@property不設置對應的@setter,該屬性就是可讀的。好比

 

class Student(object):
    
    @property
    def age(self):
        return 89

s = Student()

print(s.age)  # 89
s.age = 30  # AttributeError: can't set attribute

 

多重繼承

沒想到,這貨還能夠多重繼承。方式就是,在定義類的時候,括號裏寫上全部父類。就實現了多重繼承。好比

class Animal(object):
    def eat(self):
        print("i can eat")

class RunnableMixin(object):
    def run(self):
        print("i can run")

class Dog(Animal, RunnableMixin):
    pass

d = Dog()
d.eat()
d.run()  # 同時繼承到了eat()和run()方法

 

在設計類的繼承關係時,一般,主線都是單一繼承下來的,例如,Ostrich繼承自Bird。可是,若是須要「混入」額外的功能,經過多重繼承就能夠實現,好比,讓Ostrich除了繼承自Bird外,再同時繼承Runnable這種設計一般稱之爲MixIn。把Runnable寫成RunnableMixin是爲了更好的看出繼承關係。

定製類

__str__

這玩意有點像Java的toString()方法。Java中print一個對象名的時候好像是會去調用toString()方法的,會返回一個內存地址啥的。__str__有點這個意思。

 

class Person(object):
    pass

print(Person())  # <__main__.Person object at 0x02F67810>

 

class Person(object):
    def __str__(self):
        return ("A Person Object")

print(Person())  # A Person Object

 

__iter__

若是一個類想被用於for ... in循環,相似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代對象,而後,Python的for循環就會不斷調用該迭代對象的__next__()方法拿到循環的下一個值,直到遇到StopIteration錯誤時退出循環

上面這段描述有點Java的迭代器的感受,實現一個接口。怎麼怎麼樣的。。。

斐波那契數列

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 50:
            raise StopIteration()

        return self.a
    
for n in Fib():
    print(n)

 

__getitem__

可讓上面的Fib像調用list那樣直接取第幾個數,好比Fib()[5],其實也就是像前面的__str__同樣,python在出現方法後面跟[],Fib()[5]這種類型的時候會去調用__getitem__這個函數。你實現了這個方法就能用這種形式,沒實現這樣調用就報錯。

    def __getitem__(self, n):
        a, b = 1, 1
        for n in range(n):
            a, b = b, a + b
        return a

 

__getattr__

這個有點想Lua的元表裏面的__index 元方法,當訪問不存在的屬性時,就會去調用這個方法。好比

class Person(object):
    pass

print(Person().name)  # AttributeError: 'Person' object has no attribute 'name'
class Person(object):
    def __getattr__(self, attr):
        return "NONE"

print(Person().name)  # NOE

 

__call__

通常調用一個方法是 instance.method() 這種形式的,可是__call__ 能夠實現 instance()這種形式調用

class Person(object):
    def __init__(self, name):
        self.name = name
    
    def __call__(self):
        print("My name is %s" % self.name)

p = Person("kobe")
p()  # My name is kobe

去判斷一個對象是否能被調用,能被調用的就是一個callable對象,好比callable(Person())就返回True

 

枚舉

偷個懶。。。

 

元類

動態語言和靜態語言最大的不一樣,就是函數和類的定義,不是編譯時定義的,而是運行時動態建立的。

type

type能夠查看對象的類型,也能夠動態建立類

Car = type("Car", (object,), dict(fn = lambda self:"drive me"))
c = Car()
print(c.fn())  # drive me

要建立一個class對象,type()函數依次傳入3個參數:

  1. class的名稱;試了下,上例中type裏面的Car換成其餘也行的,這裏不知道有沒有其餘規定。
  2. 繼承的父類集合,注意Python支持多重繼承,若是隻有一個父類,別忘了tuple的單元素寫法;
  3. class的方法名稱與函數綁定,這裏咱們把函數fn綁定到方法名hello上。

錯誤、調試、測試

python也是採用像Java那種try...catch方式。格式以下,執行規則與Java同樣

try:
    print("try")
    r = 10/0
    print("result:", r)
except ZeroDivisionError as e:
    print("except:", e)
finally:
    print("finally")
print("end")

不過不一樣的是,能夠能夠在except後面加個 else表示當沒有錯誤的時候執行

一樣的,全部錯誤繼承自BaseException,當有多個except的時候,捕獲錯誤的時候,會把全部的子類錯誤也捕獲到。

也能夠自定義錯誤。

 

記錄錯誤

Python內置的logging模塊能夠很是容易地記錄錯誤信息,import logging後,代碼出錯了,會打印出錯誤信息,而且也會繼續運行下去.

 

拋出錯誤

使用raise關鍵字。這裏自定義一個錯誤

 

class MyError(ValueError):
    pass

def fun():
    raise MyError()

fun()

 

Traceback (most recent call last):
File "e:\LearnPython\try.py", line 7, in <module>
fun()
File "e:\LearnPython\try.py", line 5, in fun
raise MyError()
MyError
儘可能使用內置的錯誤類型。
另一種處理錯誤的方式
def foo(s):
    n = int(s)
    if n==0:
        raise ValueError('invalid value: %s' % s)
    return 10 / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise

bar()

捕獲到錯誤後繼續 raise 拋出,由於當前函數不知道如何處理,因此繼續向上級調用者拋出。

 

assert

斷言,格式以下

 

def fun(n):
    assert n != 0, "n is zero"
    print('fun')
fun(0)

 

AssertionError: n is zero

能夠用 -O 參數關掉assert,好比, python -O test.py ,那麼assert語句就至關於pass了

 

logging

assert比,logging不會拋出錯誤,並且能夠輸出到文件

pdb

單步調試,好的IDE PyCharm

單元測試

一個測試類,須要繼承自unittest.TestCase。類裏面的以test開頭的方法就是測試方法,不以test開頭的方法不被認爲是測試方法,測試的時候不會被執行。

unittest.TestCase提供了一些內置的判斷,好比assertEqual()

setUp()、tearDown()。沒測,估計是在測試類運行先後結束的時候分別調用的。

 

IO

一般的格式

 

try:
    f = open('E:/t.txt', 'r')
    print(f.read())
finally:
    if f:
        f.close()
    

 

和Java同樣,打開了記得關閉,爲了防止打開文件出錯,後面的close執行不到,因此用try catch這種方式,r 表示讀文件。

Python還提供一種更簡潔的方式,效果和上面的同樣。

 

with open("E:/t.txt", "r") as f:
    print(f.read())

 

read()方法會一次性讀取所有內容,若是內容有好幾個G,內存就爆了,因此,用read(size)屢次反覆調用是更合理的方法

readline()能夠每次讀取一行內容,調用readlines()一次讀取全部內容並按行返回list

 

讀取二進制文件(圖片,視屏等),  rb

 

with open("E:/t.PNG", "rb") as f:
    print(f.read())

讀取GBK編碼的文件

with open("E:/t.txt", "r", encoding='gbk') as f:
    print(f.read())

忽略錯誤(好比編碼錯誤)

with open("E:/t.txt", "r", encoding='gbk', errors="ignore") as f:
    print(f.read())

寫文件。參數有 w , 二進制文件寫 wb。操做系統每每不會馬上把數據寫入磁盤,而是放到內存緩存起來,空閒的時候再慢慢寫入。只有調用close()方法時,操做系統才保證把沒有寫入的數據所有寫入磁盤。忘記調用close()的後果是數據可能只寫了一部分到磁盤,剩下的丟失了。因此,仍是用with語句來得保險

StringIO

從內存中讀寫str,區別於從文件讀寫

from io import StringIO
#
f = StringIO("Hello World\nLearn Python")  # 初始化一個StringIO
while True:
    s = f.readline()
    if s == "":
        break
    print(s)

#
f = StringIO()
f.write("Hello Python")  # write方法會返回寫入的字符數,好比這裏是12
print(f.getvalue())

StringIO操做的只能是str,若是要操做二進制數據,就須要使用BytesIO

 

操做文件和目錄

import os
os.name  # nt
os.environ  # 打印出環境變量
os.environ.get("PATH")  # 經過get加上key來獲取

操做文件和目錄的函數一部分放在os模塊中,一部分放在os.path模塊中

os.path.abspath('.')  # 獲取絕對路徑,有點像pwd
os.mkdir("E:/testdir")  # 建立目錄
os.rmdir("E:/testdir")  # 刪除目錄

把兩個路徑合成一個時,不要直接拼字符串,而要經過os.path.join()函數,這樣能夠正確處理不一樣操做系統的路徑分隔符

一樣的道理,要拆分路徑時,也不要直接去拆字符串,而要經過os.path.split()函數,這樣能夠把一個路徑拆分爲兩部分,後一部分老是最後級別的目錄或文件名

os.path.splitext()能夠直接讓你獲得文件擴展名

os.path.splitext("E:/t.txt")  # ('E:/t', '.txt') 獲得一個tuple

重命名 os.rename 

刪除文件 os.remove

複製文件沒有提供,能夠用IO來實現,shutil模塊也提供了copyfile()的函數

序列化

Python中的序列化叫作pickling,反序列化叫作unpickling,Python提供pickle模塊來實現序列化

序列化:

import pickle
d = dict(name = "kobe", num = 24)
with open("D:/t.txt", "wb") as f:
    pickle.dump(d, f)  # 序列化後保存到文件中

文件中的內容是這樣的一堆亂七八糟的東西:

(dp0
S'num'
p1
I24
sS'name'
p2
S'kobe'
p3
s.

反序列化

with open("D:/t.txt", "rb") as f:
    p = pickle.load(f)  # load反序列化出對象
print(p)  # {'num': 24, 'name': 'kobe

反序列化出來的對象跟原來的那個不是同一個對象,只是內容相同而已

pickle可能會在不一樣版本的Python中不兼容,因此,最好只用這個保存不重要的數據。反序列化不了也不要緊。。。(因此,就是本身玩玩就好

 

JSON

爲了在不一樣的編程語言之間傳遞對象,必須把對象序列化成標準格式。好比XML,JSON,最好是JSON,由於JSON表示出來就是一個字符串,能夠被全部語言讀取。也能夠方便地存儲到磁盤或者經過網絡傳輸。JSON不只是標準格式,而且比XML更快,並且能夠直接在Web頁面中讀取,很是方便。

總之,JSON大法好,嗯。

import json
# dumps返回一個標準的json字符串
print(json.dumps(d))  # {"num": 24, "name": "kobe"}
# dump把json字符串寫到文件中
with open("D:/t.txt", "w") as f:
    json.dump(d, f)

# 一樣有個load方法把json數據反序列化
with open("D:/t.txt", "r") as f:
    j = json.load(f)
    print(j)

類的序列化

就像Java中序列化一個對象須要他的類實現了某些接口。Python中也不能直接序列化,除了那些原本就支持的數據類型,自定義類的話,要本身寫個轉換方法。好比

class Player(object):
    def __init__(self, name, team, age):
        self.name = name
        self.team = team
        self.age = age

def player2dict(p):
    return {
        "name":p.name,
        "age":p.age,
        "team":p.team
    }
p = Player("kobe", "Lakers", 35)
print(json.dumps(p, default=player2dict))  # {"age": 35, "name": "kobe", "team": "Lakers"}

或者偷懶,寫成這樣:

json.dumps(p, default=lambda o:o.__dict__)
一般class的實例都有一個__dict__屬性,它就是一個dict,用來存儲實例變量。也有少數例外,好比定義了__slots__的class。
反序列化也要寫一個函數轉換,object_hook負責把dict轉換成player
class Player(object):
    def __init__(self, name, team, age):
        self.name = name
        self.team = team
        self.age = age

def player2dict(p):
    return {
        "name":p.name,
        "age":p.age,
        "team":p.team
    }
p = Player("kobe", "Lakers", 35)
str = json.dumps(p, default=lambda o:o.__dict__)  # {"age": 35, "name": "kobe", "team": "Lakers"}
def dict2player(d):
    return Player(d["name"], d["age"], d["team"])
s = json.loads(str, object_hook=dict2player)
print(s)  # <__main__.Player object at 0x03B901F0>

 

進程和線程

線程是最小的執行單元,而進程由至少一個線程組成。如何調度進程和線程,徹底由操做系統決定,程序本身不能決定何時執行,執行多長時間

Windows下啓動子進程

 

from multiprocessing import Process
import os

def fun(name):
    print("Run child proc %s (%s)..." % (name, os.getpid()))

if __name__=="__main__":
    print("parent proc %s." % os.getpid())
    p = Process(target=fun, args=("test", ))
    print("child proc will start")
    p.start()
    p.join()
    print("child proc end.")

 

parent proc 14780.
child proc will start
Run child proc test (15192)...
child proc end.

 

注意:這玩意原本用vs code直接F5,是不會執行fun函數的,可是在終端 py test.py 是能夠的!

 

Pool

若是要啓動大量的子進程,能夠用進程池的方式批量建立子進程

 

from multiprocessing import Pool
import os, time, random

def task(name):
    print("Run task %s (%s)" % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print("task %s run %0.2f seconds" % (name, end - start))

if __name__ == "__main__":
    print("parent proc %s " % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(task, args = (i,))
    print("waiting for all subprocess done...")
    p.close()
    p.join()
    print("all subprocess done")
parent proc 10660
waiting for all subprocess done...
Run task 0 (13592)
Run task 1 (5020)
Run task 2 (5180)
Run task 3 (7012)
task 2 run 1.03 seconds
Run task 4 (5180)
task 1 run 2.06 seconds
task 3 run 2.30 seconds
task 0 run 2.78 seconds
task 4 run 1.98 seconds
all subprocess done

Pool對象調用join()方法會等待全部子進程執行完畢,調用join()以前必須先調用close(),調用close()以後就不能繼續添加新的Process了。

task 0,1,2,3是馬上執行的,而task 4要等待前面某個task完成後才執行,這是由於Pool的默認大小在個人電腦上是4,所以,最多同時執行4個進程。這是Pool有意設計的限制,並非操做系統的限制。

 

進程的通訊

Python的multiprocessing模塊包裝了底層的機制,提供了QueuePipes等多種方式來交換數據

from multiprocessing import Process, Queue
import os, time, random

def write(q):
    print("write process (%s)" % os.getpid())
    for i in ["kobe", "tracy", "nash"]:
        print("put %s to queue" % i)
        q.put(i)
        time.sleep(random.random())

def read(q):
    print("read process (%s)" % os.getpid())
    while True:
        v = q.get(True)
        print("get %s from queue" % v)

if __name__ == "__main__":
    q = Queue()
    wp = Process(target=write, args=(q,))
    rp = Process(target=read, args=(q,))
    wp.start()
    rp.start()
    wp.join()
    rp.terminate()

rp是死循環,因此不能用join()來等待結束,只能直接殺死他

write process (6576)
put kobe to queue
read process (6032)
get kobe from queue
put tracy to queue
get tracy from queue
put nash to queue
get nash from queue

多線程

Python的標準庫提供了兩個模塊:_threadthreading_thread是低級模塊,threading是高級模塊,對_thread進行了封裝。絕大多數狀況下,咱們只須要使用threading這個高級模塊

啓動一個線程就是把一個函數傳入並建立Thread實例,而後調用start()開始執行

 

import time, threading

def loop():
    print('thread %s is running' % threading.current_thread().name)
    for i in range(5):
        print('thread %s >>> %s ' % (threading.current_thread().name, i))
        time.sleep(1)
    print('thread %s ended' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='loopThread')  # 建立Thread對象,賦值要運行的函數,以及線程名字
t.start()  # 開始
t.join()  # 等待結束
print('thread %s ended' % threading.current_thread().name)

 

thread MainThread is running...
thread loopThread is running
thread loopThread >>> 0 
thread loopThread >>> 1 
thread loopThread >>> 2 
thread loopThread >>> 3 
thread loopThread >>> 4 
thread loopThread ended
thread MainThread ended

 

Lock

多線程和多進程最大的不一樣在於,多進程中,同一個變量,各自有一份拷貝存在於每一個進程中,互不影響,而多線程中,全部變量都由全部線程共享,因此,任何一個變量均可以被任何一個線程修改

嗯。只要有線程,就會要討論同步的問題

基本使用方式就這樣

 

lock = threading.Lock()  # 獲取Lock對象
n = 0
def count():
    for i in range(100):
        lock.acquire()  # 獲取鎖,放到須要數據同步的地方
        try:
            global n  # 關鍵代碼寫在try catch中
            n = n + 1
            print("thread %s runing count >>> n = %s \n" % (threading.current_thread().name, n))
        finally:
            lock.release()  # 記得在finally裏面釋放鎖

 

Python的線程雖然是真正的線程,但解釋器執行代碼時,有一個GIL鎖:Global Interpreter Lock,任何Python線程執行前,必須先得到GIL鎖,而後,每執行100條字節碼,解釋器就自動釋放GIL鎖,讓別的線程有機會執行。這個GIL全局鎖實際上把全部線程的執行代碼都給上了鎖,因此,多線程在Python中只能交替執行,即便100個線程跑在100核CPU上,也只能用到1個核

Python解釋器因爲設計時有GIL全局鎖,致使了多線程沒法利用多核。多線程的併發在Python中就是一個美麗的夢

 

分佈式進程

在Thread和Process中,應當優選Process,由於Process更穩定,並且,Process能夠分佈到多臺機器上,而Thread最多隻能分佈到同一臺機器的多個CPU上。

Python的multiprocessing模塊不但支持多進程,其中managers子模塊還支持把多進程分佈到多臺機器上。一個服務進程能夠做爲調度者,將任務分佈到其餘多個進程中,依靠網絡通訊。因爲managers模塊封裝很好,沒必要了解網絡通訊的細節,就能夠很容易地編寫分佈式多進程程序。

 

這玩意在Linux下運行沒有問題。windows下面不行。

網絡編程

TCP/IP

IP協議負責把數據從一臺計算機經過網絡發送到另外一臺計算機。數據被分割成一小塊一小塊,而後經過IP包發送出去。因爲互聯網鏈路複雜,兩臺計算機之間常常有多條線路,所以,路由器就負責決定如何把一個IP包轉發出去。IP包的特色是按塊發送,途徑多個路由,但不保證能到達,也不保證順序到達。

TCP協議則是創建在IP協議之上的。TCP協議負責在兩臺計算機之間創建可靠鏈接,保證數據包按順序到達。TCP協議會經過握手創建鏈接,而後,對每一個IP包編號,確保對方按順序收到,若是包丟掉了,就自動重發。

許多經常使用的更高級的協議都是創建在TCP協議基礎上的,好比用於瀏覽器的HTTP協議、發送郵件的SMTP協議等。

相關文章
相關標籤/搜索