Python复习 基础知识
什么是動(dòng)態(tài)語言呢? 可以簡單的理解為直接被解釋執(zhí)行的語言稱為動(dòng)態(tài)語言。
而需要編譯環(huán)境將程序轉(zhuǎn)換為其它的編碼再執(zhí)行的語言稱為靜態(tài)語言。
當(dāng)前靜態(tài)語言有:java、C/C++、C#、DELPHI、VB等。
動(dòng)態(tài)語言有:asp、php、cgi、lisp、Perl、python,Smalltalk、Ruby等。
?
數(shù)據(jù)類型:?
dict:無序,可變,大括號(hào),唯一內(nèi)置的映射類型,字典是作為哈希表(支持快速檢索的數(shù)據(jù)結(jié)構(gòu))來實(shí)現(xiàn)的。
tuple:有序,不可變,有in,無append、extend;無remove、pop;無index
list:有序,可變,小括號(hào)
?
字典 dict:?
2.7文檔:https://docs.python.org/2/library/stdtypes.html#mapping-types-dict
key,鍵必須獨(dú)一無二,可以hash,支持 __hash__方法。list無 __hash__ 方法,所以不能作為key.
values containing lists, dictionaries or other mutable types (that are compared by value rather than by object identity) may not be used as keys.
Python中可哈希的對(duì)象有:
- 數(shù)值、字符串,以及只含有數(shù)值或字符串的元組
- 用戶自定義類的實(shí)例(默認(rèn)是可哈希的,也可以通過實(shí)現(xiàn)__hash__()和__cmp__()來修改默認(rèn)行為)
value,值可以取任何數(shù)據(jù)類型,但必須是不可變的,如字符串,數(shù)或元組。
dict:和list比較有以下幾個(gè)特點(diǎn):
list:
所以,dict是用空間來換取時(shí)間的一種方法。
tuple:
用 Tuple 的好處
Tuple 比 list 操作速度快.如果您定義了一個(gè)值的常量集,并且唯一要用它做的是不斷地遍歷它,請(qǐng)使用 tuple 代替 list.
如果對(duì)不需要修改的數(shù)據(jù)進(jìn)行 “寫保護(hù)”,可以使代碼更安全.使用 tuple 而不是 list 如同擁有一個(gè)隱含的 assert 語句,說明這一數(shù)據(jù)是常量.如果必須要改變這些值,則需要執(zhí)行 tuple 到 list 的轉(zhuǎn)換
Tuple 與 list 的轉(zhuǎn)換
Tuple 可以轉(zhuǎn)換成 list,反之亦然.內(nèi)置的 tuple 函數(shù)接收一個(gè) list,并返回一個(gè)有著相同元素的 tuple
而 list 函數(shù)接收一個(gè) tuple 返回一個(gè) list.從效果上看,tuple 凍結(jié)一個(gè) list,而 list 解凍一個(gè) tuple.
Tuple 的其他應(yīng)用
一次賦多值
>>> v = ('a','b','e')
>>> (x,y,z) = v
?
參數(shù)傳值
函數(shù):Python允許你在list或tuple前面加一個(gè)*號(hào),把list或tuple的元素變成可變參數(shù)傳進(jìn)去:
參數(shù)組合:
參數(shù)定義的順序必須是:必選參數(shù)、默認(rèn)參數(shù)、可變參數(shù)和關(guān)鍵字參數(shù)。
Python的函數(shù)具有非常靈活的參數(shù)形態(tài),既可以實(shí)現(xiàn)簡單的調(diào)用,又可以傳入非常復(fù)雜的參數(shù)。
默認(rèn)參數(shù)一定要用不可變對(duì)象,如果是可變對(duì)象,運(yùn)行會(huì)有邏輯錯(cuò)誤!
要注意定義可變參數(shù)和關(guān)鍵字參數(shù)的語法:
*args是可變參數(shù),args接收的是一個(gè)tuple;
**kw是關(guān)鍵字參數(shù),kw接收的是一個(gè)dict。
以及調(diào)用函數(shù)時(shí)如何傳入可變參數(shù)和關(guān)鍵字參數(shù)的語法:
可變參數(shù)既可以直接傳入:func(1, 2, 3),又可以先組裝list或tuple,再通過*args傳入:func(*(1, 2, 3));
關(guān)鍵字參數(shù)既可以直接傳入:func(a=1, b=2),又可以先組裝dict,再通過**kw傳入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的習(xí)慣寫法,當(dāng)然也可以用其他參數(shù)名,但最好使用習(xí)慣用法。
?
遞歸函數(shù)
防止棧溢出
解決遞歸調(diào)用棧溢出的方法是通過尾遞歸優(yōu)化
def fact(n):return fact_iter(n, 1)def fact_iter(num, product):if num == 1:return productreturn fact_iter(num - 1, num * product)可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數(shù)本身,num - 1和num * product在函數(shù)調(diào)用前就會(huì)被計(jì)算,不影響函數(shù)調(diào)用。
尾遞歸事實(shí)上和循環(huán)是等價(jià)的,沒有循環(huán)語句的編程語言只能通過尾遞歸實(shí)現(xiàn)循環(huán)。
切片
L[0:3] //若第一個(gè)索引是0,還可以省略 L[:3]//取倒數(shù)第一個(gè)元素
L[-1]
迭代
dict默認(rèn)迭代是key
可以用 for value in d.itervalues()
若要同時(shí)迭代 key和value
可以用 for k,v in d.iteritems()
判斷一個(gè)對(duì)象是否是可迭代對(duì)象(返回BOOL類型):isinstance('abc', Iterable)?
?
列表生成式(List Comprehensions)
[x*x for x in range(1,11)if x % 2 == 0][4,16,36,64,100]
[m + n for m in 'ABC' for n in 'XYZ']['AX','AY','AZ','BX','BY','BZ','CX','CY','CZ']
?
生成器(generator)
一邊循環(huán)一邊計(jì)算,把列表生成器 的[] 改為 ()
generator 保存的是算法
斐波那契數(shù)列 Fibonacci
遞歸
def fib2(n):if n == 0:return 0elif n ==1:return 1return fib2(n-1) + fib2(n-2)print(fib2(7))memo = {0:0, 1:1} def fib3(n):if not n in memo:memo[n] = fib3(n-1) + fib3(n-2)return memo[n]print(fib3(7))(迭代解法)
def fib(max):n, a, b = 0, 0, 1while n < max:print ba, b = b, a+bn = n + 1generator :
def fib(max):n, a, b = 0, 0, 1while n < max:yield b a, b = b, a + bn = n+1與函數(shù)區(qū)別:
函數(shù)是順序執(zhí)行,遇到return或最后就返回。而變成generator的函數(shù)后,在調(diào)用 next()的時(shí)候執(zhí)行,遇到 yield 語句 返回,再次執(zhí)行時(shí)從上次返回的yield 語句處繼續(xù)執(zhí)行
?
函數(shù)式編程(Functional Programming)
函數(shù)式編程的一個(gè)特點(diǎn)就是,允許把函數(shù)本身作為參數(shù)傳入另一個(gè)函數(shù),還允許返回一個(gè)函數(shù)!? Python對(duì)函數(shù)式編程提供部分支持。由于Python允許使用變量,因此,Python不是純函數(shù)式編程語言。
?
高階函數(shù)(Higher -order function)
1,變量可以指向函數(shù)
2,函數(shù)名也是變量
3,傳入函數(shù)
函數(shù)可以接受另一個(gè)函數(shù)作為參數(shù),稱為高階函數(shù)
map / reduce 函數(shù)
map(),高階函數(shù),接收兩個(gè)參數(shù),一個(gè)是函數(shù),一個(gè)是序列,map將傳入的函數(shù)依次作用到序列的每個(gè)元素,并把結(jié)果作為新的list返回。
將list所有數(shù)字轉(zhuǎn)為字符串
>>>map(str, [1,2,3,4,5,6,7,8,9]) ['1', '2', '3', '4', '5', '6', '7', '8', '9']reduce 必須接收兩個(gè)參數(shù)
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 利用 map() 函數(shù),把用戶輸入的不規(guī)范的英文名字,變?yōu)槭鬃帜复髮?其他小寫的規(guī)范名字。輸 入: ['adam', 'LISA', 'barT'] ,輸出: ['Adam', 'Lisa', 'Bart'] 。Python提供的 sum() 函數(shù)可以接受一個(gè)list并求和,請(qǐng)編寫一個(gè) prod() 函數(shù),可以接受一個(gè)list并利用 reduce() 求 積。
?
def capi(w):return w.capitalize() print map(capi,L)filter:
def is_odd(n):return n % 2 == 1filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]) # 結(jié)果: [1, 5, 9, 15]sorted:
高階函數(shù),可以接受一個(gè)比較函數(shù)來實(shí)現(xiàn)自定義的排序。
比如倒序函數(shù):
def rebversed_cmp(x, y):if x > y:return -1if x < y:return 1return 0傳入自定義的比較函數(shù) reversed_cmp,就可以實(shí)現(xiàn)倒序排序:
sorted([36, 5, 12, 9, 21], reversed_cmp)?
返回函數(shù) ,閉包
注意到返回的函數(shù)在其定義內(nèi)部引用了局部變量args。
所以,當(dāng)一個(gè)函數(shù)返回了一個(gè)函數(shù)后,其內(nèi)部的局部變量還被新函數(shù)引用,所以,閉包用起來簡單,實(shí)現(xiàn)起來可不容易。
另一個(gè)需要注意的問題是,返回的函數(shù)并沒有立刻執(zhí)行,而是直到調(diào)用了f()才執(zhí)行。我們來看一個(gè)例子:
def count():fs = []for i in range(1, 4):def f():return i*ifs.append(f)return fs f1, f2, f3 = count()在上面的例子中,每次循環(huán),都創(chuàng)建了一個(gè)新的函數(shù),然后,把創(chuàng)建的3個(gè)函數(shù)都返回了。
你可能認(rèn)為調(diào)用f1(),f2()和f3()結(jié)果應(yīng)該是1,4,9,但實(shí)際結(jié)果是:
>>> f1() 9 >>> f2() 9 >>> f3() 9全部都是9!(即都循環(huán)了三次)。原因就在于返回的函數(shù)引用了變量i,但它并非立刻執(zhí)行。等到3個(gè)函數(shù)都返回時(shí),它們所引用的變量i已經(jīng)變成了3,因此最終結(jié)果為9。
返回閉包時(shí)牢記的一點(diǎn)就是:返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量。
如果一定要引用循環(huán)變量怎么辦?方法是再創(chuàng)建一個(gè)函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值,無論該循環(huán)變量后續(xù)如何更改,已綁定到函數(shù)參數(shù)的值不變:
>>> def count(): ... fs = [] ... for i in range(1, 4): ... def f(j): ... def g(): ... return j*j ... return g ... fs.append(f(i)) ... return fs ... >>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4 >>> f3() 9缺點(diǎn)是代碼較長,可利用lambda函數(shù)縮短代碼。:
f1, f2 = [(lambda j = i : j ** 2) for i in range(1, 3)]?for后面的i 是個(gè)全局變量,在傳遞進(jìn)函數(shù)參數(shù)時(shí)已經(jīng)被作為某個(gè)數(shù)字了,分解開就是
f1() = lambda j = 1 : j**2
f2() = lambda j = 2 : j**2
?
lambda:匿名函數(shù)
將一個(gè)List里的每個(gè)元素都平方,
map(lambda x: x*x, [y for y in range(10)])?
裝飾器:
?將函數(shù)賦值給變量
?函數(shù)對(duì)象有一個(gè) __name__ 屬性 ,可以拿到函數(shù)的名字
?在代碼運(yùn)行期間 動(dòng)態(tài) 增加功能的方式,稱之為“裝飾器”(Decorator)
def log(func):def wrapper(*args, **kw):print 'call %s():' % func.__name__return func(*args, **kw)return wrapper@log def now()print '123'調(diào)用now()函數(shù)
>>>now()call now():123 @log def now()print '123' 相當(dāng)于: now = log(now)log()是一個(gè)decorator,所以,原來的now()函數(shù)仍然存在,只是現(xiàn)在同名的now變量指向了新的函數(shù),于是調(diào)用now()將執(zhí)行新函數(shù),即在log()函數(shù)中返回的wrapper()函數(shù)。
wrapper()函數(shù)的參數(shù)定義是(*args, **kw),因此,wrapper()函數(shù)可以接受任意參數(shù)的調(diào)用。在wrapper()函數(shù)內(nèi),首先打印日志,再緊接著調(diào)用原始函數(shù)。
?
在面向?qū)ο?#xff08;OOP)的設(shè)計(jì)模式中,decorator被稱為裝飾模式。OOP的裝飾模式需要通過繼承和組合來實(shí)現(xiàn),而Python除了能支持OOP的decorator外,直接從語法層次支持decorator。Python的decorator可以用函數(shù)實(shí)現(xiàn),也可以用類實(shí)現(xiàn)。
decorator可以增強(qiáng)函數(shù)的功能,定義起來雖然有點(diǎn)復(fù)雜,但使用起來非常靈活和方便。
?
偏函數(shù)(partial function)
屬于 functools 模塊里邊。把一個(gè)函數(shù)的某些參數(shù)給固定住(即設(shè)置默認(rèn)值),返回一個(gè)新的函數(shù)。
>>> import functools >>> int2 = functools.partial(int, base = 2) >>> int2('1000000') 64 >>> int('1010101') 85?相當(dāng)于:
kw = {base:2)
int('10010', **kw)
?
模塊:
一個(gè) .py 文件就稱之為一個(gè)模塊(module)
#!/usr/bin/env python # -*- coding:utf-8 -*-'a test module'__author__='Michael Liao'import sysdef test():args = sys.argvif len(args)==1:print 'Hello, world!'elif len(args)==2:print 'Hello, %s!' % args[1]else:print 'Too many arguments!'if __name__=='__main__':test()第1行和第2行是標(biāo)準(zhǔn)注釋,第1行注釋可以讓這個(gè)hello.py文件直接在Unix/Linux/Mac上運(yùn)行,第2行注釋表示.py文件本身使用標(biāo)準(zhǔn)UTF-8編碼;
第4行是一個(gè)字符串,表示模塊的文檔注釋,任何模塊代碼的第一個(gè)字符串都被視為模塊的文檔注釋;
第6行使用__author__變量把作者寫進(jìn)去,這樣當(dāng)你公開源代碼后別人就可以瞻仰你的大名;
以上就是Python模塊的標(biāo)準(zhǔn)文件模板
使用sys模塊的第一步,就是導(dǎo)入該模塊:import sys
sys模塊有一個(gè)argv變量,用list存儲(chǔ)了命令行的所有參數(shù)。argv至少有一個(gè)元素,因?yàn)榈谝粋€(gè)參數(shù)永遠(yuǎn)是該.py文件的名稱。例如:
運(yùn)行python hello.py獲得的sys.argv就是['hello.py'];
運(yùn)行python hello.py Michael獲得的sys.argv就是['hello.py', 'Michael]。
if __name__=='__main__':test()當(dāng)我們?cè)诿钚羞\(yùn)行hello模塊文件時(shí),Python解釋器把一個(gè)特殊變量__name__置為__main__,而如果在其他地方導(dǎo)入該hello模塊時(shí),if判斷將失敗,因此,這種if測(cè)試可以讓一個(gè)模塊通過命令行運(yùn)行時(shí)執(zhí)行一些額外的代碼,最常見的就是運(yùn)行測(cè)試。
?
?Python是動(dòng)態(tài)語言,函數(shù)簽名一致接口就一樣,因此,無論導(dǎo)入哪個(gè)模塊后續(xù)代碼都能正常工作。
函數(shù)簽名就是函數(shù)的聲明信息,包括參數(shù)(個(gè)數(shù),類型)、返回值、調(diào)用約定之類。
安裝第三方模塊:
例如圖片:
先安裝pip: sudo easy_install pip 然后安裝PIL: sudo -H pip install Pillow 最后導(dǎo)入Image模塊: import PIL.Image 或者 from PIL import Image
?
#!/usr/bin/python #coding:utf-8#獲取當(dāng)前路徑 import os print os.getcwd()from PIL import Image im = Image.open('test.png') #打印格式、大小、類型 print im.format, im.size, im.mode #縮略圖 im.thumbnail((200,100)) im.save('thumb.jpg', 'JPEG')?
使用 __future__(python3.x 和 2.x 的區(qū)別)
1,編碼
python2.x里的字符串用'xxx'表示str,Unicode字符串用u'xxx'表示unicode
而在3.x中,所有字符串都被視為unicode,因此,寫u'xxx'和'xxx'是完全一致的,而在2.x中以'xxx'表示的str就必須寫成b'xxx',以此表示“二進(jìn)制字符串”。
?為了適應(yīng)Python 3.x的新的字符串的表示方法,在2.7版本的代碼中,可以通過unicode_literals來使用Python 3.x的新的語法:
?
#still running on Python 2.7from __future__ import unicode_literals print '\'xxx\' is unicode?', isinstance('xxx',unicode) print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode) print '\'xxx\' is str?', isinstance('xxx', str) print 'b\'xxx\' is str?', isinstance(b'xxx', str) 'xxx' is unicode? True u'xxx' is unicode? True 'xxx' is str? False b'xxx' is str? True2,除法
Python2.x 都是地板除
Python3.x 普通除法是精確除法。地板除用//
from __future__ import divisionprint '10 / 3 = ', 10 / 3 print '10.0 / 3 = ', 10.0 / 3 print '10 // 3 = ', 10 //310 / 3 =? 3.33333333333
10.0 / 3 =? 3.33333333333
10 // 3 =? 3
3,其他。如 Python3 中無xrange
http://chenqx.github.io/2014/11/10/Key-differences-between-Python-2-7-x-and-Python-3-x/
?
?
面向?qū)ο缶幊?br />Object Oriented Programming, OOP。 數(shù)據(jù)封裝、繼承、多態(tài)
把對(duì)象作為程序的基本單元,一個(gè)對(duì)象包含了數(shù)據(jù)和操作數(shù)據(jù)段函數(shù)
OOP: 對(duì)象、消息
面向過程:一組函數(shù)的順序執(zhí)行
給對(duì)象發(fā)消息實(shí)際上就是調(diào)用對(duì)象對(duì)應(yīng)的關(guān)聯(lián)函數(shù),我們稱之為對(duì)象的方法(Method)。面向?qū)ο蟮某绦驅(qū)懗鰜砭拖襁@樣:
bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score()類、實(shí)例、對(duì)象、方法
封裝
訪問限制:
要讓內(nèi)部屬性不被外部訪問,可以把屬性的名稱前加上兩個(gè)下劃線 __
變量名如果以 __ (兩個(gè)_) 開頭,就變成了一個(gè)私有變量 (private),? 只有內(nèi)部可以訪問,外部不能訪問。
class Student(object):def __init__(self, name, score):self.__name = nameself.__score = scoredef print_score(self):print '%s: %s' % (self.__name, self.__score)改完后,對(duì)于外部代碼來說,沒什么變動(dòng),但是已經(jīng)無法從外部訪問實(shí)例變量.__name和實(shí)例變量.__score了
如果外部代碼要獲取 name 和 score 可以 給 Student 類增加 get_name 和 get_score 這樣的方法:
class Student(object):...def get_name(self):return self.__namedef get_score(self):return self.__score修改屬性,使用set
class Student(object):...def set_score(self, score):self.__score = score使用方法修改name的優(yōu)點(diǎn):可以對(duì)參數(shù)做檢查,避免傳入無效的參數(shù)
在Python中,以雙下劃線開頭并結(jié)尾的,是特殊變量。可以直接訪問,不是private變量。
繼承和多態(tài)
繼承:好處,獲得父類的全部功能
多態(tài):具體調(diào)用的方法,由運(yùn)行時(shí)該對(duì)象的確切類型決定。
開閉原則:
對(duì)擴(kuò)展開放:允許新增Animal子類;
對(duì)修改封閉:不需要修改依賴Animal類型的run_twice()等函數(shù)。
有繼承才能有多態(tài)
?
獲取對(duì)象信息:
使用type
>>>type(123)<type 'int'>>>> type(abs)<type 'builtin_function_or_method'>>>> import types>>> type('abc') == types.StringTypeTrue使用dir(), 獲取一個(gè)對(duì)象的所有屬性和方法。
在len()內(nèi)部,自動(dòng)去調(diào)用該對(duì)象中的__len__()方法,下面代碼是等價(jià)的
>>> len('ABC') 3 >>> 'ABC'.__len__() 3使用 __slots__
限制Class的 屬性,只允許對(duì)Student 實(shí)例添加name和age屬性
>>> class Student(object): ... __slots__ = ('name', 'age') #用tuple定義允許綁定的屬性名稱 ...使用@property
在set_score里邊設(shè)置檢查參數(shù)
class Student(object):def get_score(self):return self._scoredef set_score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 and value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value以上復(fù)雜,可以使用 @property 裝飾器(即把函數(shù)當(dāng)成參數(shù)傳入,增加方法),把一個(gè)方法變成屬性調(diào)用。
class Student(object)@property #負(fù)責(zé)把一個(gè)getter 方法變成屬性def score(self):return self._score@score.setter #負(fù)責(zé)把一個(gè)setter方法變成屬性賦值def score(self,value):def score(self, value):if not isinstance(value, int):raise ValueError('score must be an integer!')if value < 0 or value > 100:raise ValueError('score must between 0 ~ 100!')self._score = value>>> s = Student()
>>> s.score = 60 # OK,實(shí)際轉(zhuǎn)化為s.set_score(60)
>>> s.score # OK,實(shí)際轉(zhuǎn)化為s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
?
多重繼承
class Bat(Mammal, Flyable):passMixin
?非單獨(dú)的屬性,例如RunnableMixin
Python自帶了TCPServer和UDPServer這兩類網(wǎng)絡(luò)服務(wù),而要同時(shí)服務(wù)多個(gè)用戶就必須使用多進(jìn)程或多線程模型,這兩種模型由ForkingMixin和ThreadingMixin提供。通過組合,我們就可以創(chuàng)造出合適的服務(wù)來。
?
由于Python允許使用多重繼承,因此,Mixin就是一種常見的設(shè)計(jì)。
只允許單一繼承的語言(如Java)不能使用Mixin的設(shè)計(jì)。
?
定制類:
__str__ 方法
class Student (object)def __int__(self, name):self.name = namedef __str__(self):return 'Student object (name: %s)' % self.name>>> print Student('Michael')
Student object (name:Michael)
直接敲變量不用print,打印出來的實(shí)例還是不好看:
>>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310>這是因?yàn)橹苯语@示變量調(diào)用的不是__str__(),而是__repr__(),兩者的區(qū)別是__str__()返回用戶看到的字符串,而__repr__()返回程序開發(fā)者看到的字符串,也就是說,__repr__()是為調(diào)試服務(wù)的。
解決辦法是再定義一個(gè)__repr__()。但是通常__str__()和__repr__()代碼都是一樣的,所以,有個(gè)偷懶的寫法:
__iter__ 迭代
__getitem__按照下標(biāo)取出元素
__getattr__ 動(dòng)態(tài)返回一個(gè)屬性
__call__
class Student(object):def __init__(self, name):self.name = namedef __call__(self):print('My name is %s.' % self.name)>>> s = Student('Michael')
>>> s()
My name is Michael.
使用元類,metaclass
可以使用 type() 動(dòng)態(tài)創(chuàng)建類以外,還可以使用 元類
即當(dāng)我們定義了類以后,就可以根據(jù)這個(gè)類創(chuàng)建出實(shí)例。所以先定義類,然后創(chuàng)建實(shí)例。
但是如果我們想創(chuàng)建出類呢?那就必須根據(jù)metaclass創(chuàng)建出類,所以:先定義metaclass,然后創(chuàng)建類。
?
連接起來就是:先定義metaclass,就可以創(chuàng)建類,最后創(chuàng)建實(shí)例。
?
錯(cuò)誤處理:
try...except...finally
Python 所有的錯(cuò)誤都是從 BaseException 類派生的
調(diào)用堆棧如果錯(cuò)誤沒有被捕獲,它就會(huì)一直往上拋,最后被Python解釋器捕獲,打印一個(gè)錯(cuò)誤信息,然后程序退出。
logging,可以記錄錯(cuò)誤信息。打印錯(cuò)誤后會(huì)繼續(xù)執(zhí)行,并正常退出。
def main():try:bar('0')except StandardError, e:logging.exception(2)使用 raise 拋出錯(cuò)誤。
?
調(diào)試:
使用assert
# err.pydef foo(s):n = int(s)assert n != 0, 'n is zero!'return 10/n def main():foo('0')
使用 -0 來關(guān)閉 assert
$ python -O err.py
使用 logging
使用pdb
pdb.set_trace()
# err.py import pdbs = '0' n = int(s) pdb.set_trace() # 運(yùn)行到這里會(huì)自動(dòng)暫停 print 10 / n運(yùn)行代碼,程序會(huì)自動(dòng)在pdb.set_trace()暫停并進(jìn)入pdb調(diào)試環(huán)境,可以用命令p查看變量,或者用命令c繼續(xù)運(yùn)行:
$ python err.py > /Users/michael/Github/sicp/err.py(7)<module>() -> print 10 / n (Pdb) p n 0 (Pdb) c Traceback (most recent call last):File "err.py", line 7, in <module>print 10 / n ZeroDivisionError: integer division or modulo by zero雖然用IDE調(diào)試起來比較方便,但是最后你會(huì)發(fā)現(xiàn),logging才是終極武器。
單元測(cè)試:
測(cè)試驅(qū)動(dòng)開發(fā),TDD:Test-Driven Development
單元測(cè)試是是用來對(duì)一個(gè)模塊、一個(gè)函數(shù)或者一個(gè)類來進(jìn)行正確性檢驗(yàn)的測(cè)試工作
編寫單元測(cè)試時(shí),我們需要編寫一個(gè)測(cè)試類,從unittest.TestCase繼承。
運(yùn)行單元測(cè)試:
$ python -m unittest mydict_test在命令行通過參數(shù) -m unittest 直接運(yùn)行單元測(cè)試
setUp與tearDown
可以在單元測(cè)試中編寫兩個(gè)特殊的setUp()和tearDown()方法。這兩個(gè)方法會(huì)分別在每調(diào)用一個(gè)測(cè)試方法的前后分別被執(zhí)行。
setUp()和tearDown()方法有什么用呢?設(shè)想你的測(cè)試需要啟動(dòng)一個(gè)數(shù)據(jù)庫,這時(shí),就可以在setUp()方法中連接數(shù)據(jù)庫,在tearDown()方法中關(guān)閉數(shù)據(jù)庫,這樣,不必在每個(gè)測(cè)試方法中重復(fù)相同的代碼:
?
文檔測(cè)試
Python 內(nèi)置的“文檔測(cè)試”(doctest)模塊可以直接提取注釋中的代碼并執(zhí)行測(cè)試。
IO編程
異步IO與同步IO
異步性能好,效率高,復(fù)雜(包括輪詢模式和回調(diào)模式)
文件讀寫:
>>> f = open('/User/michael/test.txt', 'r')使用try。。finally
try:f = open('/path/to/file', 'r')print f.read() finally:if f:f.close()簡潔方法,使用with語句自動(dòng)調(diào)用close() 方法:
with open('/path/to/file', 'r') as f:print f.read()調(diào)用read()會(huì)一次性讀取文件的全部內(nèi)容,如果文件有10G,內(nèi)存就爆了,所以,要保險(xiǎn)起見,可以反復(fù)調(diào)用read(size)方法,每次最多讀取size個(gè)字節(jié)的內(nèi)容。另外,調(diào)用readline()可以每次讀取一行內(nèi)容,調(diào)用readlines()一次讀取所有內(nèi)容并按行返回list。因此,要根據(jù)需要決定怎么調(diào)用。
如果文件很小,read()一次性讀取最方便;如果不能確定文件大小,反復(fù)調(diào)用read(size)比較保險(xiǎn);如果是配置文件,調(diào)用readlines()最方便:
for line in f.readlines():print(line.strip()) # 把末尾的'\n'刪掉讀文件
要以讀文件的模式打開一個(gè)文件對(duì)象,使用Python內(nèi)置的open()函數(shù),傳入文件名和標(biāo)示符:
>>> f = open('/Users/michael/test.txt', 'r')標(biāo)示符'r'表示讀,這樣,我們就成功地打開了一個(gè)文件。
如果文件不存在,open()函數(shù)就會(huì)拋出一個(gè)IOError的錯(cuò)誤,并且給出錯(cuò)誤碼和詳細(xì)的信息告訴你文件不存在:
>>> f=open('/Users/michael/notfound.txt', 'r') Traceback (most recent call last):File "<stdin>", line 1, in <module> IOError: [Errno 2] No such file or directory: '/Users/michael/notfound.txt'如果文件打開成功,接下來,調(diào)用read()方法可以一次讀取文件的全部內(nèi)容,Python把內(nèi)容讀到內(nèi)存,用一個(gè)str對(duì)象表示:
>>> f.read() 'Hello, world!'最后一步是調(diào)用close()方法關(guān)閉文件。文件使用完畢后必須關(guān)閉,因?yàn)槲募?duì)象會(huì)占用操作系統(tǒng)的資源,并且操作系統(tǒng)同一時(shí)間能打開的文件數(shù)量也是有限的:
>>> f.close()由于文件讀寫時(shí)都有可能產(chǎn)生IOError,一旦出錯(cuò),后面的f.close()就不會(huì)調(diào)用。所以,為了保證無論是否出錯(cuò)都能正確地關(guān)閉文件,我們可以使用try ... finally來實(shí)現(xiàn):
try:f = open('/path/to/file', 'r')print f.read() finally: if f: f.close()但是每次都這么寫實(shí)在太繁瑣,所以,Python引入了with語句來自動(dòng)幫我們調(diào)用close()方法:
with open('/path/to/file', 'r') as f:print f.read()這和前面的try ... finally是一樣的,但是代碼更佳簡潔,并且不必調(diào)用f.close()方法。
調(diào)用read()會(huì)一次性讀取文件的全部內(nèi)容,如果文件有10G,內(nèi)存就爆了,所以,要保險(xiǎn)起見,可以反復(fù)調(diào)用read(size)方法,每次最多讀取size個(gè)字節(jié)的內(nèi)容。另外,調(diào)用readline()可以每次讀取一行內(nèi)容,調(diào)用readlines()一次讀取所有內(nèi)容并按行返回list。因此,要根據(jù)需要決定怎么調(diào)用。
如果文件很小,read()一次性讀取最方便;如果不能確定文件大小,反復(fù)調(diào)用read(size)比較保險(xiǎn);如果是配置文件,調(diào)用readlines()最方便:
for line in f.readlines():print(line.strip()) # 把末尾的'\n'刪掉file-like Object
像open()函數(shù)返回的這種有個(gè)read()方法的對(duì)象,在Python中統(tǒng)稱為file-like Object。除了file外,還可以是內(nèi)存的字節(jié)流,網(wǎng)絡(luò)流,自定義流等等。file-like Object不要求從特定類繼承,只要寫個(gè)read()方法就行。
StringIO就是在內(nèi)存中創(chuàng)建的file-like Object,常用作臨時(shí)緩沖。
二進(jìn)制文件
前面講的默認(rèn)都是讀取文本文件,并且是ASCII編碼的文本文件。要讀取二進(jìn)制文件,比如圖片、視頻等等,用'rb'模式打開文件即可:
>>> f = open('/Users/michael/test.jpg', 'rb') >>> f.read() '\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六進(jìn)制表示的字節(jié)字符編碼
要讀取非ASCII編碼的文本文件,就必須以二進(jìn)制模式打開,再解碼。比如GBK編碼的文件:
>>> f = open('/Users/michael/gbk.txt', 'rb') >>> u = f.read().decode('gbk') >>> u u'\u6d4b\u8bd5' >>> print u 測(cè)試如果每次都這么手動(dòng)轉(zhuǎn)換編碼嫌麻煩(寫程序怕麻煩是好事,不怕麻煩就會(huì)寫出又長又難懂又沒法維護(hù)的代碼),Python還提供了一個(gè)codecs模塊幫我們?cè)谧x文件時(shí)自動(dòng)轉(zhuǎn)換編碼,直接讀出unicode:
import codecs with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f: f.read() # u'\u6d4b\u8bd5'寫文件
寫文件和讀文件是一樣的,唯一區(qū)別是調(diào)用open()函數(shù)時(shí),傳入標(biāo)識(shí)符'w'或者'wb'表示寫文本文件或?qū)懚M(jìn)制文件:
>>> f = open('/Users/michael/test.txt', 'w') >>> f.write('Hello, world!') >>> f.close()你可以反復(fù)調(diào)用write()來寫入文件,但是務(wù)必要調(diào)用f.close()來關(guān)閉文件。當(dāng)我們寫文件時(shí),操作系統(tǒng)往往不會(huì)立刻把數(shù)據(jù)寫入磁盤,而是放到內(nèi)存緩存起來,空閑的時(shí)候再慢慢寫入。只有調(diào)用close()方法時(shí),操作系統(tǒng)才保證把沒有寫入的數(shù)據(jù)全部寫入磁盤。忘記調(diào)用close()的后果是數(shù)據(jù)可能只寫了一部分到磁盤,剩下的丟失了。所以,還是用with語句來得保險(xiǎn):
with open('/Users/michael/test.txt', 'w') as f:f.write('Hello, world!')要寫入特定編碼的文本文件,請(qǐng)效仿codecs的示例,寫入unicode,由codecs自動(dòng)轉(zhuǎn)換成指定編碼。
?
操作文件和目錄
# 查看當(dāng)前目錄的絕對(duì)路徑: >>> os.path.abspath('.') '/Users/michael' # 在某個(gè)目錄下創(chuàng)建一個(gè)新目錄, # 首先把新目錄的完整路徑表示出來: >>> os.path.join('/Users/michael', 'testdir') '/Users/michael/testdir' # 然后創(chuàng)建一個(gè)目錄: >>> os.mkdir('/Users/michael/testdir') # 刪掉一個(gè)目錄: >>> os.rmdir('/Users/michael/testdir')拆分路徑:
>>> os.path.split('/Users/michael/testdir/file.txt') ('/Users/michael/testdir', 'file.txt')過濾文件:
>>> [x for x in os.listdir('.') if os.path.isdir(x)] ['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Adlm', 'Applications', 'Desktop', ...]要列出所有的.py文件,也只需一行代碼:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py'] ['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']?
序列化:(pickling)
把變量從內(nèi)存中變成可存儲(chǔ)或傳輸?shù)倪^程稱之為序列化
把變量從內(nèi)存中變成可存儲(chǔ)或傳輸?shù)倪^程稱之為反序列化, unpickling
序列化,兩個(gè)模塊,cPickle、pickle,cPickle是C語言寫的pickle是Python寫的
序列化后就可以寫入磁盤,或者網(wǎng)絡(luò)傳輸。
>>> d = dict(name='Bob', age=20, score=88) >>> pickle.dumps(d) # 把任意一個(gè)對(duì)象序列化成一個(gè) str,然后,就可以把這個(gè)str寫入文件。或者用 pickle.dump() >>> f = open('dump.txt', 'wb') >>> pickle.dump(d, f) #把一個(gè)對(duì)象序列化后寫入一個(gè) file-like Object: >>> f.close()當(dāng)我們要把對(duì)象從磁盤讀到內(nèi)存時(shí),可以先把內(nèi)容讀到一個(gè)str,然后用pickle.loads()方法反序列化出對(duì)象,也可以直接用pickle.load()方法從一個(gè)file-like Object中直接反序列化出對(duì)象。我們打開另一個(gè)Python命令行來反序列化剛才保存的對(duì)象:
>>> f = open('dump.txt', 'rb') >>> d = pickle.load(f) >>> f.close() >>> d {'age': 20, 'score': 88, 'name': 'Bob'}JSON
JSON不僅是標(biāo)準(zhǔn)格式,并且比XML更快,而且可以直接在Web頁面中讀取,非常方便。
JSON表示的對(duì)象就是標(biāo)準(zhǔn)的JavaScript語言的對(duì)象
JSON和Python內(nèi)置的數(shù)據(jù)類型對(duì)應(yīng)如下:
| JSON類型 | Python類型 |
| {} | dict |
| [] | list |
| "string" | 'str'或u'unicode' |
| 1234.56 | int或float |
| true/false | True/False |
| null | None |
?
dumps()方法還提供了一大堆的可選參數(shù):
https://docs.python.org/2/library/json.html#json.dumps
將對(duì)象轉(zhuǎn)為 JSON
?
進(jìn)程和線程
多任務(wù)的實(shí)現(xiàn)有3種方式:
- 多進(jìn)程模式;
- 多線程模式;
- 多進(jìn)程+多線程模式。
多進(jìn)程(multiprocessing):
Unix/Linux操作系統(tǒng)提供了一個(gè)fork()系統(tǒng)調(diào)用,它非常特殊。普通的函數(shù)調(diào)用,調(diào)用一次,返回一次,但是fork()調(diào)用一次,返回兩次,因?yàn)椴僮飨到y(tǒng)自動(dòng)把當(dāng)前進(jìn)程(稱為父進(jìn)程)復(fù)制了一份(稱為子進(jìn)程),然后,分別在父進(jìn)程和子進(jìn)程內(nèi)返回。
子進(jìn)程永遠(yuǎn)返回0,而父進(jìn)程返回子進(jìn)程的ID。這樣做的理由是,一個(gè)父進(jìn)程可以fork出很多子進(jìn)程,所以,父進(jìn)程要記下每個(gè)子進(jìn)程的ID,而子進(jìn)程只需要調(diào)用getppid()就可以拿到父進(jìn)程的ID。
#Python的os模塊封裝了常見的系統(tǒng)調(diào)用,其中就包括fork,可以在Python程序中輕松創(chuàng)建子進(jìn)程:# multiprocessing.py import osprint 'Process (%s) start...' % os.getpid() pid = os.fork() if pid==0:print 'I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()) else:print 'I (%s) just created a child process (%s).' % (os.getpid(), pid)#運(yùn)行結(jié)果如下: Process (876) start... I (876) just created a child process (877). I am child process (877) and my parent is 876.#有了fork調(diào)用,一個(gè)進(jìn)程在接到新任務(wù)時(shí)就可以復(fù)制出一個(gè)子進(jìn)程來處理新任務(wù),常見的Apache服務(wù)器就是由父進(jìn)程監(jiān)聽端口,每當(dāng)有新的http請(qǐng)求時(shí),
就fork出子進(jìn)程來處理新的http請(qǐng)求。
multiprocessing
multiprocessing模塊提供了一個(gè)Process類來代表一個(gè)進(jìn)程對(duì)象,
下面的例子演示了啟動(dòng)一個(gè)子進(jìn)程并等待其結(jié)束:
from multiprocessing import Process import os# 子進(jìn)程要執(zhí)行的代碼 def run_proc(name):print 'Run child process %s (%s)...' % (name, os.getpid())if __name__=='__main__':print 'Parent process %s.' % os.getpid()p = Process(target=run_proc, args=('test',))#創(chuàng)建子進(jìn)程時(shí),只需要傳入一個(gè)執(zhí)行函數(shù)和函數(shù)的參數(shù),print 'Process will start.'p.start()#創(chuàng)建一個(gè)Process實(shí)例,用start()方法啟動(dòng),這樣創(chuàng)建進(jìn)程比fork()還要簡單。p.join() #join() 方法可以等待子進(jìn)程結(jié)束后再繼續(xù)往下運(yùn)行,通常用于進(jìn)程間的同步。print 'Process end.' Parent process 928. Process will start. Run child process test (929)... Process end.?
Pool
如果要啟動(dòng)大量的子進(jìn)程,可以用進(jìn)程池的方式批量創(chuàng)建子進(jìn)程:
# -*- coding:utf-8 -*-from multiprocessing import Pool import os, time, randomdef long_time_task(name):print 'Run task %s (%s)...' % (name, os.getpid())start = time.time()time.sleep(random.random() * 3)end = time.time()print 'Task %s runs %0.2f seconds.' % (name, (end - start))if __name__=='__main__':print 'Parent process %s.' % os.getpid()p = Pool()for i in range(5):p.apply_async(long_time_task, args=(i,))print 'Waiting for all subprocesses done...'p.close()p.join() //print 'All subprocesses done.' Parent process 862. Waiting for all subprocesses done... Run task 0 (864)... Run task 1 (865)... Run task 2 (866)... Run task 3 (867)... Task 1 runs 0.42 seconds. Run task 4 (865)... Task 0 runs 1.41 seconds. Task 4 runs 1.65 seconds. Task 3 runs 2.42 seconds. Task 2 runs 2.64 seconds. All subprocesses done.***Repl Closed***解讀:
對(duì)Pool對(duì)象調(diào)用join()方法會(huì)等待所有子進(jìn)程執(zhí)行完畢,調(diào)用join()之前必須先調(diào)用close(),調(diào)用close()之后就不能繼續(xù)添加新的Process了。
請(qǐng)注意輸出的結(jié)果,task 0,1,2,3是立刻執(zhí)行的,而task 4要等待前面某個(gè)task完成后才執(zhí)行,這是因?yàn)镻ool的默認(rèn)大小在我的電腦上是4,因此,最多同時(shí)執(zhí)行4個(gè)進(jìn)程。這是Pool有意設(shè)計(jì)的限制,并不是操作系統(tǒng)的限制。如果改成:
p = Pool(5)就可以同時(shí)跑5個(gè)進(jìn)程。
由于Pool的默認(rèn)大小是CPU的核數(shù),如果你不幸擁有8核CPU,你要提交至少9個(gè)子進(jìn)程才能看到上面的等待效果。
多線程
Python的標(biāo)準(zhǔn)庫提供了兩個(gè)模塊:thread和threading,thread是低級(jí)模塊,threading是高級(jí)模塊,對(duì)thread進(jìn)行了封裝。絕大多數(shù)情況下,我們只需要使用threading這個(gè)高級(jí)模塊。
啟動(dòng)一個(gè)線程就是把一個(gè)函數(shù)傳入并創(chuàng)建Thread實(shí)例,然后調(diào)用start()開始執(zhí)行:
?
import time, threading#新線程執(zhí)行的代碼: def loop():print 'thread %s is running...' % threading.current_thread().namen = 0while n < 5:n = n + 1print 'thread %s >>> %s' % (threading.current_thread().name, n)time.sleep(1)print 'thread %s ended.' % threading.current_thread().nameprint 'thread %s is running...' % threading.current_thread().name t = threading.Thread(target = loop, name = 'LoopThread') t.start() t.join() #join作用是阻塞主進(jìn)程(擋住,無法執(zhí)行join以后的語句 print 'thread %s ended.' % threading.current_thread().name thread MainThread is running... thread LoopThread is running... thread LoopThread >>> 1 thread LoopThread >>> 2 thread LoopThread >>> 3 thread LoopThread >>> 4 thread LoopThread >>> 5 thread LoopThread ended. thread MainThread ended.***Repl Closed***join ()方法:
主線程A中,創(chuàng)建了子線程B,并且在主線程A中調(diào)用了B.join(),那么,
主線程A會(huì)在調(diào)用的地方等待,直到子線程B完成操作后,才可以接著往下執(zhí)行,
那么在調(diào)用這個(gè)線程時(shí)可以使用被調(diào)用線程的join方法。
?原型join([timeout]) 里面的參數(shù)時(shí)可選的,代表線程運(yùn)行的最大時(shí)間,
即如果超過這個(gè)時(shí)間,不管這個(gè)此線程有沒有執(zhí)行完畢都會(huì)被回收,
然后主線程或函數(shù)都會(huì)接著執(zhí)行的。
?
由于任何進(jìn)程默認(rèn)就會(huì)啟動(dòng)一個(gè)線程,我們把該線程稱為主線程,主線程又可以啟動(dòng)新的線程,
Python的threading模塊有個(gè)current_thread()函數(shù),它永遠(yuǎn)返回當(dāng)前線程的實(shí)例。
主線程實(shí)例的名字叫MainThread,子線程的名字在創(chuàng)建時(shí)指定,我們用LoopThread命名子線程。
名字僅僅在打印時(shí)用來顯示,完全沒有其他意義,
如果不起名字Python就自動(dòng)給線程命名為Thread-1,Thread-2……
?
Lock
多線程和多進(jìn)程最大的不同在于,
多進(jìn)程中,同一個(gè)變量,各自有一份拷貝存在于每個(gè)進(jìn)程中,互不影響,
多線程中,所有變量都由所有進(jìn)程共享,所以,任何一個(gè)變量都可以被任何一個(gè)線程修改,
因此,線程之間共享數(shù)據(jù)最大的危險(xiǎn)在于多個(gè)線程同時(shí)改一個(gè)變量,把內(nèi)容改亂了。
?
鎖,當(dāng)某個(gè)線程開始執(zhí)行change_it()時(shí),我們說,該線程因?yàn)楂@得了鎖,
因此其他線程不能同時(shí)執(zhí)行change_it(),只能等待,直到鎖被釋放后,獲得該鎖以后才能改。
由于鎖只有一個(gè),無論多少線程,同一時(shí)刻最多只有一個(gè)線程持有該鎖,
所以,不會(huì)造成修改的沖突。創(chuàng)建一個(gè)鎖就是通過threading.Lock()來實(shí)現(xiàn):
balance = 0 lock = threading.Lock()def run_thread(n):for i in range(100000):# 先要獲取鎖: lock.acquire()try:# 放心地改吧: change_it(n)finally:# 改完了一定要釋放鎖:lock.release()鎖的好處就是確保了某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾完整地執(zhí)行,壞處當(dāng)然也很多,首先是阻止了多線程并發(fā)執(zhí)行,包含鎖的某段代碼實(shí)際上只能以單線程模 式執(zhí)行,效率就大大地下降了。其次,由于可以存在多個(gè)鎖,不同的線程持有不同的鎖,并試圖獲取對(duì)方持有的鎖時(shí),可能會(huì)造成死鎖,導(dǎo)致多個(gè)線程全部掛起,既 不能執(zhí)行,也無法結(jié)束,只能靠操作系統(tǒng)強(qiáng)制終止。
?
用C、C++或Java來改寫相同的死循環(huán),直接可以把全部核心跑滿,4核就跑到400%,8核就跑到800%,為什么Python不行呢?
因?yàn)镻ython的線程雖然是真正的線程,但解釋器執(zhí)行代碼時(shí),有一個(gè)GIL鎖:Global Interpreter Lock,任何Python線程執(zhí)行前,必須先獲得GIL鎖,然后,每執(zhí)行100條字節(jié)碼,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行。這個(gè)GIL全局鎖實(shí)際上把所有線程的執(zhí)行代碼都給上了鎖,所以,多線程在Python中只能交替執(zhí)行,即使100個(gè)線程跑在100核CPU上,也只能用到1個(gè)核。
GIL是Python解釋器設(shè)計(jì)的歷史遺留問題,通常我們用的解釋器是官方實(shí)現(xiàn)的CPython,要真正利用多核,除非重寫一個(gè)不帶GIL的解釋器。
所以,在Python中,可以使用多線程,但不要指望能有效利用多核。如果一定要通過多線程利用多核,那只能通過C擴(kuò)展來實(shí)現(xiàn),不過這樣就失去了Python簡單易用的特點(diǎn)。
不過,也不用過于擔(dān)心,Python雖然不能利用多線程實(shí)現(xiàn)多核任務(wù),但可以通過多進(jìn)程實(shí)現(xiàn)多核任務(wù)。多個(gè)Python進(jìn)程有各自獨(dú)立的GIL鎖,互不影響。
?
ThreadLocal
目的:
在多線程環(huán)境下,每個(gè)線程都有自己的數(shù)據(jù)。一個(gè)線程使用自己的局部變量比使用全局變量好,因?yàn)榫植孔兞恐挥芯€程自己能看見,不會(huì)影響其他線程,而全局變量的修改必須加鎖。
但是局部變量也有問題,就是在函數(shù)調(diào)用的時(shí)候,傳遞起來很麻煩
# -*- coding:utf-8 -*-import threading#創(chuàng)建全局ThreadLocal對(duì)象: local_school = threading.local()def process_student():print 'Hello, %s (in %s)' % (local_school.student, threading.current_thread().name)def process_thread(name):#綁定ThreadLocal的student:local_school.student = nameprocess_student()t1 = threading.Thread(target = process_thread, args = ('Alice',), name = 'Thread-A') t2 = threading.Thread(target = process_thread, args = ('Bob',), name = 'Thread-B') t1.start() t2.start() t1.join() t2.join() Hello, Alice (in Thread-A) Hello, Bob (in Thread-B)全局變量local_school就是一個(gè)ThreadLocal對(duì)象,每個(gè)Thread對(duì)它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個(gè)屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內(nèi)部會(huì)處理。
可以理解為全局變量local_school是一個(gè)dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。
ThreadLocal最常用的地方就是為每個(gè)線程綁定一個(gè)數(shù)據(jù)庫連接,HTTP請(qǐng)求,用戶身份信息等,這樣一個(gè)線程的所有調(diào)用到的處理函數(shù)都可以非常方便地訪問這些資源。
?
多線程vs多進(jìn)程
多線程:優(yōu)點(diǎn),效率高,但是任何一個(gè)線程掛掉都有可能直接造成整個(gè)進(jìn)程奔潰,
因?yàn)樗芯€程共享進(jìn)程的內(nèi)存。
多進(jìn)程:優(yōu)點(diǎn),穩(wěn)定,創(chuàng)建進(jìn)程的代價(jià)大
?
計(jì)算密集型 vs. IO密集型
是否采用多任務(wù)的第二個(gè)考慮是任務(wù)的類型。我們可以把任務(wù)分為計(jì)算密集型和IO密集型。
對(duì)于計(jì)算密集型任務(wù),最好用C語言編寫。
涉及到網(wǎng)絡(luò)、磁盤IO的任務(wù)都是IO密集型任務(wù),這類任務(wù)的特點(diǎn)是CPU消耗很少,任務(wù)的大部分時(shí)間都在等待IO操作完成(因?yàn)镮O的速度遠(yuǎn)遠(yuǎn)低于CPU 和內(nèi)存的速度)。對(duì)于IO密集型任務(wù),任務(wù)越多,CPU效率越高,但也有一個(gè)限度。常見的大部分任務(wù)都是IO密集型任務(wù),比如Web應(yīng)用。
對(duì)于IO密集型任務(wù),最合適的語言就是開發(fā)效率最高(代碼量最少)的語言,腳本語言是首選,C語言最差。
?
現(xiàn)代操作系統(tǒng)對(duì)IO操作已經(jīng)做了巨大的改進(jìn),最大的特點(diǎn)就是支持異步IO。如果充分利用操作系統(tǒng)提供的異步IO支持,就可以用單進(jìn)程單線程模型來執(zhí)行多任 務(wù),這種全新的模型稱為事件驅(qū)動(dòng)模型,Nginx就是支持異步IO的Web服務(wù)器,它在單核CPU上采用單進(jìn)程模型就可以高效地支持多任務(wù)。在多核CPU 上,可以運(yùn)行多個(gè)進(jìn)程(數(shù)量與CPU核心數(shù)相同),充分利用多核CPU。由于系統(tǒng)總的進(jìn)程數(shù)量十分有限,因此操作系統(tǒng)調(diào)度非常高效。用異步IO編程模型來 實(shí)現(xiàn)多任務(wù)是一個(gè)主要的趨勢(shì)。
?
分布式進(jìn)程
# -*- coding:utf-8 -*- taskmanager.pyimport random, time, Queue from multiprocessing.managers import BaseManger#發(fā)送任務(wù)的隊(duì)列: task_queue = Queue.Queue() #接受結(jié)果的隊(duì)列: result_queue = Queue.Queue()#從BaseManager繼承的QueueManager: class QueueManager(BaseManger):pass #把兩個(gè)Queue都注冊(cè)到網(wǎng)絡(luò)上,callable參數(shù)關(guān)聯(lián)了Queue對(duì)象: QueueManager.register('get_task_queue', callable=lambda: task_queue) QueueManager.register('get_result_queue', callable=lambda: result_queue) # 綁定端口5000, 設(shè)置驗(yàn)證碼'abc': manager = QueueManager(address=('', 5000), authkey='abc') # 啟動(dòng)Queue: manager.start() manager.start() # 獲得通過網(wǎng)絡(luò)訪問的Queue對(duì)象: task = manager.get_task_queue() result = manager.get_result_queue() # 放幾個(gè)任務(wù)進(jìn)去: for i in range(10):n = random.randint(0, 10000)print('Put task %d...' % n)task.put(n) # 從result隊(duì)列讀取結(jié)果: print('Try get results...') for i in range(10):r = result.get(timeout=10)print('Result: %s' % r) # 關(guān)閉: manager.shutdown()
請(qǐng)注意,當(dāng)我們?cè)谝慌_(tái)機(jī)器上寫多進(jìn)程程序時(shí),創(chuàng)建的Queue可以直接拿來用,但是,在分布式多進(jìn)程環(huán)境下,添加任務(wù)到Queue不可以直接對(duì)原始的task_queue進(jìn)行操作,那樣就繞過了QueueManager的封裝,必須通過manager.get_task_queue()獲得的Queue接口添加。
然后,在另一臺(tái)機(jī)器上啟動(dòng)任務(wù)進(jìn)程(本機(jī)上啟動(dòng)也可以):
# taskworker.pyimport time, sys, Queue from multiprocessing.managers import BaseManager# 創(chuàng)建類似的QueueManager: class QueueManager(BaseManager):pass# 由于這個(gè)QueueManager只從網(wǎng)絡(luò)上獲取Queue,所以注冊(cè)時(shí)只提供名字: QueueManager.register('get_task_queue') QueueManager.register('get_result_queue')# 連接到服務(wù)器,也就是運(yùn)行taskmanager.py的機(jī)器: server_addr = '127.0.0.1' print('Connect to server %s...' % server_addr) # 端口和驗(yàn)證碼注意保持與taskmanager.py設(shè)置的完全一致: m = QueueManager(address=(server_addr, 5000), authkey='abc') # 從網(wǎng)絡(luò)連接: m.connect() # 獲取Queue的對(duì)象: task = m.get_task_queue() result = m.get_result_queue() # 從task隊(duì)列取任務(wù),并把結(jié)果寫入result隊(duì)列: for i in range(10):try:n = task.get(timeout=1)print('run task %d * %d...' % (n, n))r = '%d * %d = %d' % (n, n, n*n)time.sleep(1)result.put(r)except Queue.Empty:print('task queue is empty.') # 處理結(jié)束: print('worker exit.')任務(wù)進(jìn)程要通過網(wǎng)絡(luò)連接到服務(wù)進(jìn)程,所以要指定服務(wù)進(jìn)程的IP。
現(xiàn)在,可以試試分布式進(jìn)程的工作效果了。先啟動(dòng)taskmanager.py服務(wù)進(jìn)程:
$ python taskmanager.py Put task 3411... Put task 1605... Put task 1398... Put task 4729... Put task 5300... Put task 7471... Put task 68... Put task 4219... Put task 339... Put task 7866... Try get results...taskmanager進(jìn)程發(fā)送完任務(wù)后,開始等待result隊(duì)列的結(jié)果。現(xiàn)在啟動(dòng)taskworker.py進(jìn)程:
$ python taskworker.py 127.0.0.1 Connect to server 127.0.0.1... run task 3411 * 3411... run task 1605 * 1605... run task 1398 * 1398... run task 4729 * 4729... run task 5300 * 5300... run task 7471 * 7471... run task 68 * 68... run task 4219 * 4219... run task 339 * 339... run task 7866 * 7866... worker exit.taskworker進(jìn)程結(jié)束,在taskmanager進(jìn)程中會(huì)繼續(xù)打印出結(jié)果:
Result: 3411 * 3411 = 11634921 Result: 1605 * 1605 = 2576025 Result: 1398 * 1398 = 1954404 Result: 4729 * 4729 = 22363441 Result: 5300 * 5300 = 28090000 Result: 7471 * 7471 = 55815841 Result: 68 * 68 = 4624 Result: 4219 * 4219 = 17799961 Result: 339 * 339 = 114921 Result: 7866 * 7866 = 61873956這個(gè)簡單的Manager/Worker模型有什么用?其實(shí)這就是一個(gè)簡單但真正的分布式計(jì)算,把代碼稍加改造,啟動(dòng)多個(gè)worker,就可以把任務(wù)分布到幾臺(tái)甚至幾十臺(tái)機(jī)器上,比如把計(jì)算n*n的代碼換成發(fā)送郵件,就實(shí)現(xiàn)了郵件隊(duì)列的異步發(fā)送。
Queue對(duì)象存儲(chǔ)在哪?注意到taskworker.py中根本沒有創(chuàng)建Queue的代碼,所以,Queue對(duì)象存儲(chǔ)在taskmanager.py進(jìn)程中:
而Queue之所以能通過網(wǎng)絡(luò)訪問,就是通過QueueManager實(shí)現(xiàn)的。由于QueueManager管理的不止一個(gè)Queue,所以,要給每個(gè)Queue的網(wǎng)絡(luò)調(diào)用接口起個(gè)名字,比如get_task_queue。
authkey有什么用?這是為了保證兩臺(tái)機(jī)器正常通信,不被其他機(jī)器惡意干擾。如果taskworker.py的authkey和taskmanager.py的authkey不一致,肯定連接不上。
?
?正則表達(dá)式
?
- [0-9a-zA-Z\_]可以匹配一個(gè)數(shù)字、字母或者下劃線;
-
[0-9a-zA-Z\_]+可以匹配至少由一個(gè)數(shù)字、字母或者下劃線組成的字符串,比如'a100','0_Z','Py3000'等等;
-
[a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下劃線開頭,后接任意個(gè)由一個(gè)數(shù)字、字母或者下劃線組成的字符串,也就是Python合法的變量;
-
[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精確地限制了變量的長度是1-20個(gè)字符(前面1個(gè)字符+后面最多19個(gè)字符)。
A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'。
^表示行的開頭,^\d表示必須以數(shù)字開頭。
$表示行的結(jié)束,\d$表示必須以數(shù)字結(jié)束。
你可能注意到了,py也可以匹配'python',
但是加上^py$就變成了整行匹配,就只能匹配'py'了
re模塊
Python提供re模塊,包含所有正則表達(dá)式的功能。
>>> import re >>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345') <_sre.SRE_Match object at 0x1026e18b8> >>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345') >>>match()方法判斷是否匹配,如果匹配成功,返回一個(gè)Match對(duì)象,否則返回None。常見的判斷方法就是
import retest = '用戶輸入的字符串'if re.match(r'正則表達(dá)式',test):print 'ok' else:print 'failed'切分字符串
用正則表達(dá)式切分字符串比用固定的字符更靈活,請(qǐng)看正常的切分代碼:
>>> 'a b c'.split(' ') ['a', 'b', '', '', 'c']嗯,無法識(shí)別連續(xù)的空格,用正則表達(dá)式試試:
>>> re.split(r'\s+', 'a b c') ['a', 'b', 'c']無論多少個(gè)空格都可以正常分割。加入,試試:
>>> re.split(r'[\s\,]+', 'a,b, c d') ['a', 'b', 'c', 'd']再加入;試試:
>>> re.split(r'[\s\,\;]+', 'a,b;; c d') ['a', 'b', 'c', 'd']?分組(group)
?
編譯
當(dāng)我們?cè)赑ython中使用正則表達(dá)式時(shí),re模塊內(nèi)部會(huì)干兩件事情:
編譯正則表達(dá)式,如果正則表達(dá)式的字符串本身不合法,會(huì)報(bào)錯(cuò);
用編譯后的正則表達(dá)式去匹配字符串。
re_email = re.compile(r'[a-z\.]{3,20})@([a-z]+\.[a-z]{3})')
?
常用內(nèi)建模塊
batteried included
?
collection是Python內(nèi)建的一個(gè)集合摸塊,提供了許多有用的集合類。
namedtuple
tuple 可以表示不變集合,例如,一個(gè)點(diǎn)的二維坐標(biāo)可以表示為:
>>> p = (1, 2)
nametuple 是一個(gè)函數(shù),它用來創(chuàng)建一個(gè)自定義的tuple對(duì)象
并且規(guī)定了tuple元素的個(gè)數(shù),并可以用屬性而不是索引來引用tuple的某個(gè)元素。
這樣一來,我們用namedtuple可以很方便地定義一種數(shù)據(jù)類型,它具備tuple的不變性,又可以根據(jù)屬性來引用,使用十分方便。
>>> from collections import namedtuple >>> Point = namedtuple('Point', ['x', 'y']) >>> p = Point(1, 2) >>> p.x 1 >>> p.y 2?deque
?list 訪問快,但是插入和刪除慢,因?yàn)長ist是線性存儲(chǔ)。
deque是雙向鏈表,適合用于隊(duì)列和棧
>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])
deque 可以實(shí)現(xiàn)List的:append(), pop(), appendleft(), popleft()
defultdict
使用dict時(shí),如果引用的Key不存在,就會(huì)拋出KeyError。如果希望key不存在時(shí),返回一個(gè)默認(rèn)值,就可以用defaultdict:
>>> from collections import defultdit
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1']
?'abc'
>>> dd['key2']
?'N/A'
OrderedDict
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]) >>> od # OrderedDict的Key是有序的 OrderedDict([('a', 1), ('b', 2), ('c', 3)])Counter
Counter是一個(gè)簡單的計(jì)數(shù)器,例如,統(tǒng)計(jì)字符出現(xiàn)的個(gè)數(shù):
>>> from collections import Counter >>> c = Counter() >>> for ch in 'programming': ... c[ch] = c[ch] + 1 ... >>> c Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})Counter實(shí)際上也是dict的一個(gè)子類,上面的結(jié)果可以看出,字符'g'、'm'、'r'各出現(xiàn)了兩次,其他字符各出現(xiàn)了一次。
Base64
base64是一種用64 個(gè)字符來表示任意二進(jìn)制數(shù)據(jù)的方法。
Base64編碼會(huì)把3字節(jié)的二進(jìn)制數(shù)據(jù)編碼為4字節(jié)的文本數(shù)據(jù),長度增加33%,好處是編碼后的文本數(shù)據(jù)可以在郵件正文、網(wǎng)頁等直接顯示。
對(duì)二進(jìn)制數(shù)據(jù)進(jìn)行處理,每3個(gè)字節(jié)一組,一共是3x8=24bit,劃為4組,每組正好6個(gè)bit
如果要編碼的二進(jìn)制數(shù)據(jù)不是3的倍數(shù),最后會(huì)剩下1個(gè)或2個(gè)字節(jié)怎么辦?Base64用\x00字節(jié)在末尾補(bǔ)足后,再在編碼的末尾加上1個(gè)或2個(gè)=號(hào),表示補(bǔ)了多少字節(jié),解碼的時(shí)候,會(huì)自動(dòng)去掉。
?
由于=字符也可能出現(xiàn)在Base64編碼中,但=用在URL、Cookie里面會(huì)造成歧義,所以,很多Base64編碼后會(huì)把=去掉
自動(dòng)去掉=的:
def b64decode_self(str):
return base64.b64decode(str+'='*(4-len(str)%4))
?
struct
在Python中,比方說要把一個(gè)32位無符號(hào)整數(shù)變成字節(jié),也就是4個(gè)長度的str
1bit:表示1比特
1byte:表示1字節(jié)
1byte=8bit
>>> import struct >>> struct.pack('>I', 10240099) '\x00\x9c@c'pack的第一個(gè)參數(shù)是處理指令,'>I'的意思是:>表示字節(jié)順序是big-endian,也就是網(wǎng)絡(luò)序,I表示4字節(jié)無符號(hào)整數(shù)。后面的參數(shù)個(gè)數(shù)要和處理指令一致。unpack把str變成相應(yīng)的數(shù)據(jù)類型:>>> struct.unpack('>IH', '\xf0\xf0\xf0\xf0\x80\x80') (4042322160, 32896)Windows的位圖文件(.bmp)是一種非常簡單的文件格式,我們來用struct分析一下。
首先找一個(gè)bmp文件,沒有的話用“畫圖”畫一個(gè)。
讀入前30個(gè)字節(jié)來分析:
>>> s = '\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'BMP格式采用小端方式存儲(chǔ)數(shù)據(jù),文件頭的結(jié)構(gòu)按順序如下:
兩個(gè)字節(jié):'BM'表示W(wǎng)indows位圖,'BA'表示OS/2位圖; 一個(gè)4字節(jié)整數(shù):表示位圖大小; 一個(gè)4字節(jié)整數(shù):保留位,始終為0; 一個(gè)4字節(jié)整數(shù):實(shí)際圖像的偏移量; 一個(gè)4字節(jié)整數(shù):Header的字節(jié)數(shù); 一個(gè)4字節(jié)整數(shù):圖像寬度; 一個(gè)4字節(jié)整數(shù):圖像高度; 一個(gè)2字節(jié)整數(shù):始終為1; 一個(gè)2字節(jié)整數(shù):顏色數(shù)。
所以,組合起來用unpack讀取:
>>> struct.unpack('<ccIIIIIIHH', s) ('B', 'M', 691256, 0, 54, 40, 640, 360, 1, 24)結(jié)果顯示,'B'、'M'說明是Windows位圖,位圖大小為640x360,顏色數(shù)為24。
請(qǐng)編寫一個(gè)bmpinfo.py,可以檢查任意文件是否是位圖文件,如果是,打印出圖片大小和顏色數(shù)。
import struct def test(img_dir):with open(img_dir, 'rb')as f:tu = struct.unpack('<ccIIII\IIHH', f.read(30))if tu[0] == 'B' and tu[1] == 'M':print 'windows bitmap,size:%s, width:%s,\height:%s' %(tu[2],tu[6], tu[7])elif tu[0] == 'B' and tu[1] == 'A':print 'OS\2:, width:%s, height:%s' %(tu[6], tu[7])else:print 'sorry'f.close()test('/Users/Y1Y/Desktop/1.bmp')?
?socket
# 導(dǎo)入socket庫: import socket # 創(chuàng)建一個(gè)socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立連接: s.connect(('www.sina.com.cn', 80))創(chuàng)建Socket時(shí),AF_INET指定使用IPv4協(xié)議,如果要用更先進(jìn)的IPv6,就指定為AF_INET6。SOCK_STREAM指定使用面向流的TCP協(xié)議,這樣,一個(gè)Socket對(duì)象就創(chuàng)建成功,但是還沒有建立連接。
客戶端要主動(dòng)發(fā)起TCP連接,必須知道服務(wù)器的IP地址和端口號(hào)。新浪網(wǎng)站的IP地址可以用域名www.sina.com.cn自動(dòng)轉(zhuǎn)換到IP地址,但是怎么知道新浪服務(wù)器的端口號(hào)呢?
答案是作為服務(wù)器,提供什么樣的服務(wù),端口號(hào)就必須固定下來。由于我們想要訪問網(wǎng)頁,因此新浪提供網(wǎng)頁服務(wù)的服務(wù)器必須把端口號(hào)固定在80端口,因?yàn)?0端口是Web服務(wù)的標(biāo)準(zhǔn)端口。其他服務(wù)都有對(duì)應(yīng)的標(biāo)準(zhǔn)端口號(hào),例如SMTP服務(wù)是25端口,FTP服務(wù)是21端口,等等。端口號(hào)小于1024的是Internet標(biāo)準(zhǔn)服務(wù)的端口,端口號(hào)大于1024的,可以任意使用。
因此,我們連接新浪服務(wù)器的代碼如下:
s.connect(('www.sina.com.cn', 80))注意參數(shù)是一個(gè)tuple,包含地址和端口號(hào)。
建立TCP連接后,我們就可以向新浪服務(wù)器發(fā)送請(qǐng)求,要求返回首頁的內(nèi)容:
# 發(fā)送數(shù)據(jù): s.send('GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')?
轉(zhuǎn)載于:https://www.cnblogs.com/IDRI/p/5741333.html
總結(jié)
以上是生活随笔為你收集整理的Python复习 基础知识的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab遗传算法外卖配送优化(新的约
- 下一篇: pythontkinter图片_Pyth