本文全部內容是學習期間作的筆記,僅爲我的查閱和複習方便而記錄。全部內容均摘自:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000python
字符串mysql
- 若是字符串內部既包含
'
又包含"
,能夠用轉義字符\
來轉義。- 多行字符串能夠經過
'''
字符串內容'''
來表示r''
表示''
內部的字符串默認不轉義
true
, false
;布爾值能夠用and
、or
和not
運算None
_
的組合,且不能用數字開頭
常量, 所有大寫
的變量名錶示常量git
一種除法是//
,稱爲地板除,兩個整數的除法仍然是整數web
>>> 10 // 3 3
list定義,classmates = ['Michael', 'Bob', 'Tracy']正則表達式
tuple定義,classmates = ('Michael', 'Bob', 'Tracy')算法
list可變
,tuple不變
。- list用
[]
進行定義,tuple用()
進行定義。均可以經過正整數和負數進行下標的獲取。
tuple所謂的「不變」是說,tuple的每一個元素,`指向永遠不變`。即指向'a',就不能改爲指向'b',指向一個list,就不能改爲指向其餘對象,但指向的這個list自己是可變的!
if <條件判斷1>: <執行1> elif <條件判斷2>: <執行2> elif <條件判斷3>: <執行3> else: <執行4>
注意條件判斷後面的:
,不要少寫了。sql
names = ['Michael', 'Bob', 'Tracy'] for name in names: print(name)
- range()函數,能夠生成一個整數序列,再經過list()函數能夠轉換爲list
sum = 0 n = 99 while n > 0: sum = sum + n n = n - 2 print(sum)
dict:字典,相似於map,可重複,快速查找。數據庫
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} >>> d['Michael'] 95
和list比較,dict有如下幾個特色:編程
- 查找和插入的速度極快,不會隨着key的增長而變慢;
- 須要佔用大量的內存,內存浪費多。
而list相反:json
- 查找和插入的時間隨着元素的增長而增長;
- 佔用空間小,浪費內存不多。
因此,dict是用空間來換取時間的一種方法。
set:集合,不可重複。要建立一個set,須要提供一個list做爲輸入集合
>>> s = set([1, 2, 3]) >>> s {1, 2, 3}
定義一個函數要使用def
語句,依次寫出函數名、括號、括號中的參數和冒號:
,而後,在縮進塊中編寫函數體,函數的返回值用return
語句返回。
def my_abs(x): if x >= 0: return x else: return -x
注意,函數體內部的語句在執行時,一旦執行到return
時,函數就執行完畢,並將結果返回。所以,函數內部經過條件判斷和循環能夠實現很是複雜的邏輯。
若是沒有return
語句,函數執行完畢後也會返回結果,只是結果爲None
。
return None
能夠簡寫爲return
。
def nop(): pass
pass
語句什麼都不作,
isinstance()
實現def my_abs(x): if not isinstance(x, (int, float)): raise TypeError('bad operand type') if x >= 0: return x else: return -x
好比在遊戲中常常須要從一個點移動到另外一個點,給出座標、位移和角度,就能夠計算出新的新的座標:
import math def move(x, y, step, angle=0): nx = x + step * math.cos(angle) ny = y - step * math.sin(angle) return nx, ny
- 其實返回值是一個tuple!可是,在語法上,返回一個tuple能夠省略括號,而多個變量能夠同時接收一個tuple,按位置賦給對應的值,因此,Python的函數返回多值其實就是返回一個tuple,但寫起來更方便。
定義函數時,須要肯定函數名和參數個數;
若是有必要,能夠先對參數的數據類型作檢查;
函數體內部能夠用return隨時返回函數結果;
函數執行完畢也沒有return語句時,自動return None。
函數能夠同時返回多個值,但其實就是一個tuple。
def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s
設置默認參數時,有幾點要注意:
- 一是必選參數在前,默認參數在後,不然Python的解釋器會報錯(思考一下爲何默認參數不能放在必選參數前面);
- 二是如何設置默認參數。
當函數有多個參數時,把變化大的參數放前面,變化小的參數放後面。變化小的參數就能夠做爲默認參數。
使用默認參數有什麼好處?最大的好處是能下降調用函數的難度。
** 定義默認參數要牢記一點:默認參數必須指向不變對象! 若是是list,可用L=None
來聲明 **
*
號便可。定義:
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum
調用方式1:
>>> calc(1, 2) 5 >>> calc() 0
調用方式2:可經過在list或tuple前面加一個*
號,把list或tuple的元素變成可變參數傳進去
>>> nums = [1, 2, 3] >>> calc(*nums) 14
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)
調用方式1:
>>> person('Michael', 30) name: Michael age: 30 other: {}
調用方式2:
>>> person('Bob', 35, city='Beijing') name: Bob age: 35 other: {'city': 'Beijing'} >>> person('Adam', 45, gender='M', job='Engineer') name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
調用方式3:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'} >>> person('Jack', 24, **extra) name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
def person(name, age, *args, city, job): print(name, age, args, city, job)
或
def person(name, age, *, city='Beijing', job): print(name, age, city, job)
調用方式:
>>> person('Jack', 24, job='Engineer') Jack 24 Beijing Engineer
- 要特別注意,若是沒有可變參數,就必須加一個做爲特殊分隔符。若是缺乏,Python解釋器將沒法識別位置參數和命名關鍵字參數
- Python的函數具備很是靈活的參數形態,既能夠實現簡單的調用,又能夠傳入很是複雜的參數。
- 默認參數必定要用不可變對象,若是是可變對象,程序運行時會有邏輯錯誤!
- 要注意定義可變參數和關鍵字參數的語法:
*args
是可變參數,args接收的是一個tuple;
**kw是
關鍵字參數,kw接收的是一個dict。
- 以及調用函數時如何傳入可變參數和關鍵字參數的語法:
- 可變參數既能夠直接傳入:
func(1, 2, 3)
,又能夠先組裝list或tuple,再經過*args傳入:func(*(1, 2, 3))
;
- 關鍵字參數既能夠直接傳入:
func(a=1, b=2)
,又能夠先組裝dict,再經過**kw傳入:func(**{'a': 1, 'b': 2})
。
- 使用
*args
和**kw
是Python的習慣寫法,固然也能夠用其餘參數名,但最好使用習慣用法。
- 命名的關鍵字參數是爲了限制調用者能夠傳入的參數名,同時能夠提供默認值。
- 定義命名的關鍵字參數在沒有可變參數的狀況下不要忘了寫分隔符*,不然定義的將是位置參數。
取一個list或tuple的部分元素是很是常見的操做
取list前三個:
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] >>> L[0:3] ['Michael', 'Sarah', 'Tracy']
若是第一個索引是0
,還能夠省略,如L[:3]。
支持倒數切片
>>> L = list(range(100)) >>> L [0, 1, 2, 3, ..., 99]
前10個數,每兩個取一個:
>>> L[:10:2] [0, 2, 4, 6, 8]
全部數,每5個取一個:
>>> L[::5] [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
甚至什麼都不寫,只寫[:]就能夠原樣複製一個list:
>>> L[:] [0, 1, 2, 3, ..., 99]
** tuple和字符串也能夠相似地操做 **
在Python中,迭代是經過for ... in來完成
只要是可迭代對象,不管有無下標,均可以迭代,好比dict就能夠迭代:
>>> d = {'a': 1, 'b': 2, 'c': 3} >>> for key in d: ... print(key) ... a c b
dict的存儲不是按照list的方式順序排列,因此,迭代出的結果順序極可能不同。
默認狀況下,dict迭代的是key
。若是要迭代value,能夠用for value in d.values()
,若是要同時迭代key和value,能夠用for k, v in d.items()
。
>>> from collections import Iterable >>> isinstance('abc', Iterable) # str是否可迭代 True >>> isinstance([1,2,3], Iterable) # list是否可迭代 True >>> isinstance(123, Iterable) # 整數是否可迭代 False
>>> for i, value in enumerate(['A', 'B', 'C']): ... print(i, value) ... 0 A 1 B 2 C
列表生成式即List Comprehensions,是Python內置的很是簡單卻強大的能夠用來建立list的生成式。
舉個例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]能夠用list(range(1, 11)):
>>> list(range(1, 11)) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
若是要生成[1x1, 2x2, 3x3, ..., 10x10]
,能夠這樣:
>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
建立一個包含100萬個元素的列表,不只佔用很大的存儲空間,若是咱們僅僅須要訪問前面幾個元素,那後面絕大多數元素佔用的空間都白白浪費了。
一邊循環一邊計算的機制,稱爲生成器:generator。
- generator保存的是算法,每次調用next(g),就計算出g的下一個元素的值,直到計算到最後一個元素,沒有更多的元素時,拋出StopIteration的錯誤。
- 第一種方法很簡單,只要把一個列表生成式的
[]
改爲()
,就建立了一個generator:
>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x1022ef630>
- 定義generator的另外一種方法。若是一個函數定義中包含
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'
http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014317799226173f45ce40636141b6abc8424e12b5fb27000 最難理解的就是generator和函數的執行流程不同。函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。
最難理解的就是generator和函數的執行流程不同。函數是順序執行,遇到return語句或者最後一行函數語句就返回。而變成generator的函數,在每次調用next()的時候執行,遇到yield語句返回,再次執行時從上次返回的yield語句處繼續執行。
能夠直接做用於for循環的數據類型有如下幾種:
- 一類是集合數據類型,如list、tuple、dict、set、str等;
- 一類是generator,包括生成器和帶yield的generator function。
這些能夠直接做用於for循環的對象統稱爲可迭代對象:Iterable
。
可使用isinstance()
判斷一個對象是不是Iterable
對象:
>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance('abc', Iterator) False
生成器都是Iterator
對象,但list
、dict
、str
雖然是Iterable
,卻不是Iterator
。
把list
、dict
、str
等Iterable
變成Iterator
可使用iter()
函數:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True
你可能會問,爲何list、dict、str等數據類型不是Iterator?
這是由於Python的Iterator對象表示的是一個數據流,Iterator對象能夠被next()函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration錯誤。能夠把這個數據流看作是一個有序序列,但咱們卻不能提早知道序列的長度,只能不斷經過next()函數實現按需計算下一個數據,因此Iterator的計算是惰性的,只有在須要返回下一個數據時它纔會計算。
Iterator甚至能夠表示一個無限大的數據流,例如全體天然數。而使用list是永遠不可能存儲全體天然數的。
- 凡是可做用於for循環的對象都是Iterable類型;
- 凡是可做用於next()函數的對象都是Iterator類型,它們表示一個惰性計算的序列;
- 集合數據類型如list、dict、str等是Iterable但不是Iterator,不過能夠經過iter()函數得到一個Iterator對象。
- Python的for循環本質上就是經過不斷調用next()函數實現的
>>> f = abs >>> f(-10) 10
>>> abs = 10 >>> abs(-10) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable
一個函數就能夠接收另外一個函數做爲參數
,這種函數就稱之爲高階函數
。 **def add(x, y, f): return f(x) + f(y)
調用
>>> add(-5, 6, abs) 11
高階函數除了能夠接受函數做爲參數外,還能夠把函數做爲結果值返回。
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum
當咱們調用lazy_sum()
時,返回的並非求和結果,而是求和函數:
>>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90>
調用函數f
時,才真正計算求和的結果:
>>> f() 25
在這個例子中,咱們在函數lazy_sum
中又定義了函數sum,而且,內部函數sum
能夠引用外部函數lazy_sum
的參數和局部變量,當lazy_sum
返回函數sum時,相關參數和變量都保存在返回的函數中,這種稱爲「閉包(Closure)
」的程序結構擁有極大的威力。
** 請再注意一點,當咱們調用lazy_sum()時,每次調用都會返回一個新的函數,即便傳入相同的參數:**
>>> f1 = lazy_sum(1, 3, 5, 7, 9) >>> f2 = lazy_sum(1, 3, 5, 7, 9) >>> f1==f2 False
注意到返回的函數在其定義內部引用了局部變量args,因此,當一個函數返回了一個函數後,其內部的局部變量還被新函數引用,因此,閉包用起來簡單,實現起來可不容易。
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count() >>> f1() 9 >>> f2() 9 >>> f3() 9
- 所有都是9!緣由就在於返回的函數引用了變量i,但它並不是馬上執行。等到3個函數都返回時,它們所引用的變量i已經變成了3,所以最終結果爲9。
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者後續會發生變化的變量。
若是必定要引用循環變量怎麼辦?方法是再建立一個函數,用該函數的參數綁定循環變量當前的值,不管該循環變量後續如何更改,已綁定到函數參數的值不變:
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)馬上被執行,所以i的當前值被傳入f() return fs
再看看結果:
>>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4 >>> f3() 9
一個函數能夠返回一個計算結果,也能夠返回一個函數。
返回一個函數時,牢記該函數並未執行,返回函數中不要引用任何可能會變化的變量。
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) [1, 4, 9, 16, 25, 36, 49, 64, 81]
匿名函數lambda x: x * x實際上就是:
def f(x): return x * x
關鍵字lambda
表示匿名函數,冒號前面的x
表示函數參數。
匿名函數有個限制,就是隻能有一個表達式,不用寫return
,返回值就是該表達式的結果。
用匿名函數有個好處,由於函數沒有名字,沒必要擔憂函數名衝突。此外,匿名函數也是一個函數對象,也能夠把匿名函數賦值給一個變量,再利用變量來調用該函數:
>>> f = lambda x: x * x >>> f <function <lambda> at 0x101c6ef28> >>> f(5) 25
一樣,也能夠把匿名函數做爲返回值返回,好比:
def build(x, y): return lambda: x * x + y * y
在函數調用先後自動打印日誌,但又不但願修改函數的定義,這種在代碼運行期間動態增長功能的方式,稱之爲「裝飾器」(Decorator)。
本質上,decorator就是一個返回函數的高階函數。因此,咱們要定義一個能打印日誌的decorator,能夠定義以下:
def log(func): def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper
觀察上面的log,由於它是一個decorator,因此接受一個函數做爲參數,並返回一個函數。咱們要藉助Python的@語法,把decorator置於函數的定義處:
@log def now(): print('2015-3-25')
調用now()函數,不只會運行now()函數自己,還會在運行now()函數前打印一行日誌:
>>> now() call now(): 2015-3-25
functools.partial
就是幫助咱們建立一個偏函數的,不須要咱們本身定義int2(),能夠直接使用下面的代碼建立一個新的函數int2:
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85
因此,簡單總結functools.partial的做用就是,把一個函數的某些參數給固定住(也就是設置默認值),返回一個新的函數,調用這個新函數會更簡單。
當函數的參數個數太多,須要簡化時,使用functools.partial能夠建立一個新的函數,這個新函數能夠固定住原函數的部分參數,從而在調用時更簡單。
爲了不模塊名衝突,Python又引入了按目錄來組織模塊的方法,稱爲包(Package)。
請注意,每個包目錄下面都會有一個__init__.py
的文件,這個文件是必須存在的,不然,Python就把這個目錄當成普通目錄,而不是一個包。__init__.py
能夠是空文件,也能夠有Python代碼,由於__init__.py
自己就是一個模塊,而它的模塊名就是目錄名。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' import sys def test(): args = sys.argv if 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行是標準註釋,第1行註釋可讓這個hello.py文件直接在Unix/Linux/Mac上運行,第2行註釋表示.py文件自己使用標準UTF-8編碼;
第4行是一個字符串,表示模塊的文檔註釋,任何模塊代碼的第一個字符串都被視爲模塊的文檔註釋;
第6行使用__author__變量把做者寫進去,這樣當你公開源代碼後別人就能夠瞻仰你的大名;
最後,注意到這兩行代碼:
if __name__=='__main__': test()
當咱們在命令行運行hello模塊文件時,Python解釋器把一個特殊變量__name__
置爲__main__
,而若是在其餘地方導入該hello模塊時,if判斷將失敗,所以,這種if測試可讓一個模塊經過命令行運行時執行一些額外的代碼,最多見的就是運行測試。
_
前綴來實現的。正常的函數和變量名是公開的(public),能夠被直接引用,好比:abc,x123,PI等;
相似__xxx__
這樣的變量是特殊變量,能夠被直接引用,可是有特殊用途,好比上面的__author__
,__name__
就是特殊變量,hello模塊定義的文檔註釋也能夠用特殊變量__doc__
訪問,咱們本身的變量通常不要用這種變量名;
相似_xxx
和__xxx
這樣的函數或變量就是非公開的(private),不該該被直接引用,好比_abc
,__abc
等;
之因此咱們說,private函數和變量「不該該」被直接引用,而不是「不能」被直接引用,是由於Python並無一種方法能夠徹底限制訪問private函數或變量,可是,從編程習慣上不該該引用private函數或變量。
private函數或變量不該該被別人引用,那它們有什麼用呢?請看例子:
def _private_1(name): return 'Hello, %s' % name def _private_2(name): return 'Hi, %s' % name def greeting(name): if len(name) > 3: return _private_1(name) else: return _private_2(name)
咱們在模塊裏公開greeting()函數,而把內部邏輯用private函數隱藏起來了,這樣,調用greeting()函數不用關心內部的private函數細節,這也是一種很是有用的代碼封裝和抽象的方法,即:
外部不須要引用的函數所有定義成private,只有外部須要引用的函數才定義爲public。
通常來講,第三方庫都會在Python官方的pypi.python.org 網站註冊,要安裝一個第三方庫,必須先知道該庫的名稱,能夠在官網或者pypi上搜索,好比Pillow的名稱叫Pillow,所以,安裝Pillow的命令就是:
pip install Pillow
數據封裝、繼承和多態是面向對象的三大特色
面向對象最重要的概念就是類(Class)和實例(Instance),必須牢記類是抽象的模板,好比Student類,而實例是根據類建立出來的一個個具體的「對象」,每一個對象都擁有相同的方法,但各自的數據可能不一樣。
在建立實例的時候,把一些咱們認爲必須綁定的屬性強制填寫進去。經過定義一個特殊的__init__方法,在建立實例的時候,就把name,score等屬性綁上去:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
注意到__init__
方法的第一個參數永遠是self,表示建立的實例自己,所以,在__init__
方法內部,就能夠把各類屬性綁定到self,由於self就指向建立的實例自己。
有了__init__
方法,在建立實例的時候,就不能傳入空的參數了,必須傳入與__init__
方法匹配的參數,但self不須要傳,Python解釋器本身會把實例變量傳進去:
>>> bart = Student('Bart Simpson', 59) >>> bart.name 'Bart Simpson' >>> bart.score 59
和普通的函數相比,在類中定義的函數只有一點不一樣,就是第一個參數永遠是實例變量self
,而且,調用時,不用傳遞該參數。除此以外,類的方法和普通函數沒有什麼區別,因此,你仍然能夠用默認參數、可變參數、關鍵字參數和命名關鍵字參數。
類是建立實例的模板,而實例則是一個一個具體的對象,各個實例擁有的數據都互相獨立,互不影響;
方法就是與實例綁定的函數,和普通函數不一樣,方法能夠直接訪問實例的數據;
經過在實例上調用方法,咱們就直接操做了對象內部的數據,但無需知道方法內部的實現細節。
和靜態語言不一樣,Python容許對實例變量綁定任何數據,也就是說,對於兩個實例變量,雖然它們都是同一個類的不一樣實例,但擁有的變量名稱均可能不一樣:
>>> bart = Student('Bart Simpson', 59) >>> lisa = Student('Lisa Simpson', 87) >>> bart.age = 8 >>> bart.age 8 >>> lisa.age Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age'
在Class內部,能夠有屬性和方法,而外部代碼能夠經過直接調用實例變量的方法來操做數據,這樣,就隱藏了內部的複雜邏輯。
若是要讓內部屬性不被外部訪問,能夠把屬性的名稱前加上兩個下劃線__
,在Python中,實例的變量名若是以__
開頭,就變成了一個私有變量(private),只有內部能夠訪問,外部不能訪問,因此,咱們把Student類改一改:
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score))
改完後,對於外部代碼來講,沒什麼變更,可是已經沒法從外部訪問實例變量.__name
和實例變量.__score
了:
>>> bart = Student('Bart Simpson', 98) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name'
須要注意的是,在Python中,變量名相似`__xxx__`的,也就是以雙下劃線開頭,而且以雙下劃線結尾的,是特殊變量,特殊變量是能夠直接訪問的,不是private變量,因此,不能用`__name__`、`__score__`這樣的變量名。
雙下劃線開頭的實例變量是否是必定不能從外部訪問呢?其實也不是。不能直接訪問__name是由於Python解釋器對外把__name變量改爲了_Student__name,因此,仍然能夠經過_Student__name來訪問__name變量:
>>> bart._Student__name 'Bart Simpson'
可是強烈建議你不要這麼幹,由於不一樣版本的Python解釋器可能會把__name改爲不一樣的變量名。
總的來講就是,Python自己沒有任何機制阻止你幹壞事,一切全靠自覺。
最後注意下面的這種錯誤寫法:
>>> bart = Student('Bart Simpson', 98) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 設置__name變量! >>> bart.__name 'New Name'
表面上看,外部代碼「成功」地設置了__name
變量,但實際上這個__name
變量和class內部的__name
變量不是一個變量!內部的__name
變量已經被Python解釋器自動改爲了_Student__name
,而外部代碼給bart新增了一個__name
變量。不信試試:
>>> bart.get_name() # get_name()內部返回self.__name 'Bart Simpson'
在OOP程序設計中,當咱們定義一個class的時候,能夠從某個現有的class繼承,新的class稱爲子類(Subclass),而被繼承的class稱爲基類、父類或超類(Base class、Super class)。
當子類和父類都存在相同的run()方法時,咱們說,子類的run()覆蓋了父類的run(),在代碼運行的時候,老是會調用子類的run()。這樣,咱們就得到了繼承的另外一個好處:多態
。
多態的好處就是,當咱們須要傳入Dog、Cat、Tortoise……時,咱們只須要接收Animal類型就能夠了,由於Dog、Cat、Tortoise……都是Animal類型,而後,按照Animal類型進行操做便可。因爲Animal類型有run()方法,所以,傳入的任意類型,只要是Animal類或者子類,就會自動調用實際類型的run()方法,這就是多態的意思:
對於一個變量,咱們只須要知道它是Animal類型,無需確切地知道它的子類型,就能夠放心地調用run()方法,而具體調用的run()方法是做用在Animal、Dog、Cat仍是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,無論細節,而當咱們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的「開閉」原則:
- 對擴展開放:容許新增Animal子類;
- 對修改封閉:不須要修改依賴Animal類型的run_twice()等函數。
繼承還能夠一級一級地繼承下來,就比如從爺爺到爸爸、再到兒子這樣的關係。而任何類,最終均可以追溯到根類object,這些繼承關係看上去就像一顆倒着的樹。
- 對於靜態語言(例如Java)來講,若是須要傳入Animal類型,則傳入的對象必須是Animal類型或者它的子類,不然,將沒法調用run()方法。
- 對於Python這樣的動態語言來講,則不必定須要傳入Animal類型。咱們只須要保證傳入的對象有一個run()方法就能夠了:
class Timer(object): def run(self): print('Start...')
這就是動態語言的「鴨子類型
」,它並不要求嚴格的繼承體系,一個對象只要「看起來像鴨子,走起路來像鴨子」,那它就能夠被看作是鴨子。
Python的「file-like object「就是一種鴨子類型。對真正的文件對象,它有一個read()方法,返回其內容。可是,許多對象,只要有read()方法,都被視爲「file-like object「。許多函數接收的參數就是「file-like object「,你不必定要傳入真正的文件對象,徹底能夠傳入任何實現了read()方法的對象。
繼承能夠把父類的全部功能都直接拿過來,這樣就沒必要重零作起,子類只須要新增本身特有的方法,也能夠把父類不適合的方法覆蓋重寫。
動態語言的鴨子類型特色決定了繼承不像靜態語言那樣是必須的。
當咱們拿到一個對象的引用時,如何知道這個對象是什麼類型、有哪些方法呢?
>>> type(123) <class 'int'> >>> type('str') <class 'str'> >>> type(None) <type(None) 'NoneType'>
>>> type(123)==type(456) True >>> type(123)==int True >>> type('abc')==type('123') True >>> type('abc')==str True >>> type('abc')==type(123) False
使用isinstance()
對於class的繼承關係來講,使用type()就很不方便。咱們要判斷class的類型,可使用isinstance()函數。
使用dir()
若是要得到一個對象的全部屬性和方法,可使用dir()函數,它返回一個包含字符串的list,好比,得到一個str對象的全部屬性和方法:
>>> dir('ABC') ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
僅僅把屬性和方法列出來是不夠的,配合getattr()
、setattr()
以及hasattr()
,咱們能夠直接操做一個對象的狀態:
>>> hasattr(obj, 'x') # 有屬性'x'嗎? True >>> obj.x 9 >>> hasattr(obj, 'y') # 有屬性'y'嗎? False >>> setattr(obj, 'y', 19) # 設置一個屬性'y' >>> hasattr(obj, 'y') # 有屬性'y'嗎? True >>> getattr(obj, 'y') # 獲取屬性'y' 19 >>> obj.y # 獲取屬性'y' 19
因爲Python是動態語言,根據類建立的實例能夠任意綁定屬性
。
當咱們定義了一個類屬性後,這個屬性雖然歸類全部,但類的全部實例均可以訪問到。來測試一下:
>>> class Student(object): ... name = 'Student' ... >>> s = Student() # 建立實例s >>> print(s.name) # 打印name屬性,由於實例並無name屬性,因此會繼續查找class的name屬性 Student >>> print(Student.name) # 打印類的name屬性 Student >>> s.name = 'Michael' # 給實例綁定name屬性 >>> print(s.name) # 因爲實例屬性優先級比類屬性高,所以,它會屏蔽掉類的name屬性 Michael >>> print(Student.name) # 可是類屬性並未消失,用Student.name仍然能夠訪問 Student >>> del s.name # 若是刪除實例的name屬性 >>> print(s.name) # 再次調用s.name,因爲實例的name屬性沒有找到,類的name屬性就顯示出來了 Student
數據封裝、繼承和多態只是面向對象程序設計中最基礎的3個概念。接下來咱們會討論多重繼承、定製類、元類等概念。
class Student(object): pass
>>> s = Student() >>> s.name = 'Michael' # 動態給實例綁定一個屬性 >>> print(s.name) Michael >>> def set_age(self, age): # 定義一個函數做爲實例方法 ... self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age, s) # 給實例綁定一個方法 >>> s.set_age(25) # 調用實例方法 >>> s.age # 測試結果 25
** 給一個實例綁定的方法,對另外一個實例是不起做用的 **
>>> s2 = Student() # 建立新的實例 >>> s2.set_age(25) # 嘗試調用方法 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'set_age'
>>> def set_score(self, score): ... self.score = score ... >>> Student.set_score = set_score >>> s.set_score(100) >>> s.score 100 >>> s2.set_score(99) >>> s2.score 99
- 一般狀況下,上面的set_score方法能夠直接定義在class中,但動態綁定容許咱們在程序運行的過程當中動態給class加上功能,這在靜態語言中很難實現。
若是咱們想要限制實例的屬性怎麼辦?
爲了達到限制的目的,Python容許在定義class的時候,定義一個特殊的__slots__
變量,來限制該class實例能添加的屬性:
class Student(object): __slots__ = ('name', 'age') # 用tuple定義容許綁定的屬性名稱 >>> s = Student() # 建立新的實例 >>> s.name = 'Michael' # 綁定屬性'name' >>> s.age = 25 # 綁定屬性'age' >>> s.score = 99 # 綁定屬性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
- 注意:
使用__slots__要注意,__slots__
定義的屬性僅對當前類實例起做用,** 對繼承的子類是不起做用 ** 的:
>>> class GraduateStudent(Student): ... pass ... >>> g = GraduateStudent() >>> g.score = 9999
除非在子類中也定義__slots__
,這樣,子類實例容許定義的屬性就是** 自身 ** 的__slots__
加上** 父類 ** 的__slots__
。
在綁定屬性時,若是咱們直接把屬性暴露出去,雖然寫起來很簡單,可是,沒辦法檢查參數,致使能夠把成績隨便改:
s = Student() s.score = 9999
爲了限制score的範圍,一般狀況下能夠經過一個set_score()方法來設置成績,再經過一個get_score()來獲取成績。但這略顯複雜,沒有直接用屬性這麼直接簡單。
裝飾器(decorator)能夠給函數動態加上功能嗎?對於類的方法,裝飾器同樣起做用。Python內置的@property裝飾器就是負責把一個方法變成屬性調用的:
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 an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value
只須要加上@property
就能夠了,此時,@property
自己又建立了另外一個裝飾器@屬性名.setter
,負責把一個setter方法變成屬性賦值。
>>> s = Student() >>> s.score = 60 # OK,實際轉化爲s.set_score(60) >>> s.score # OK,實際轉化爲s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last): ... ValueError: score must between 0 ~ 100!
** 還能夠定義只讀屬性,只定義getter方法,不定義setter方法就是一個只讀屬性 **
繼承是面向對象編程的一個重要的方式,由於經過繼承,子類就能夠擴展父類的功能。
經過多重繼承,一個子類就能夠同時得到多個父類的全部功能。
MixIn
MixIn的目的就是給一個類增長多個功能,這樣,在設計類的時候,咱們優先考慮經過多重繼承來組合多個MixIn的功能,而不是設計多層次的複雜的繼承關係。
小結
** 因爲Python容許使用多重繼承
,所以,MixIn就是一種常見的設計。 **
** 只容許單一繼承的語言(如Java)不能使用MixIn的設計。 **
用戶
看到的字符串使得print(實例名),以更友好的方式輸出。
開發者
看到的字符串__repr__()
是爲調試服務的
解決辦法是再定義一個__repr__()。可是一般__str__()和__repr__()代碼都是同樣的,因此,有個偷懶的寫法:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__
__iter__()
方法,該方法返回一個迭代對象,而後,Python的for循環就會不斷調用該迭代對象的__next__()
方法拿到循環的下一個值,直到遇到StopIteration錯誤時退出循環。咱們以斐波那契數列爲例,寫一個Fib類,能夠做用於for循環:
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化兩個計數器a,b def __iter__(self): return self # 實例自己就是迭代對象,故返回本身 def __next__(self): self.a, self.b = self.b, self.a + self.b # 計算下一個值 if self.a > 100000: # 退出循環的條件 raise StopIteration(); return self.a # 返回下一個值
如今,試試把Fib實例做用於for循環:
>>> for n in Fib(): ... print(n) ... 1 1 2 3 5 ... 46368 75025
__getitem__()
方法:class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
調用:
>>> f = Fib() >>> f[0] 1 >>> f[1] 1 >>> f[2] 2 >>> f[3] 3 >>> f[10] 89 >>> f[100] 573147844013817084101
若是想是想list神奇的切片方法須要對__getitem__()
方法進行改造
__getitem__()
傳入的參數多是一個int
,也多是一個切片對象slice
class Fib(object): def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L
如今試試Fib的切片:
>>> f = Fib() >>> f[0:5] [1, 1, 2, 3, 5] >>> f[:10] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 可是沒有對step參數做處理: >>> f[:10:2] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
也沒有對負數做處理,因此,要正確實現一個__getitem__()
** 仍是有不少工做要作的。**
此外,若是把對象當作dict,__getitem__()
的參數也多是一個能夠做key的object,例如str。
與之對應的是__setitem__()
方法,把對象視做list或dict來對集合賦值。最後,還有一個__delitem__()方
法,用於刪除某個元素。
總之,經過上面的方法,咱們本身定義的類表現得和Python自帶的list、tuple、dict沒什麼區別,這徹底歸功於動態語言的「鴨子類型」,不須要強制繼承某個接口。
__getattr__()
方法,動態返回一個屬性class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99
- 注意,只有在沒有找到屬性的狀況下,才調用__getattr__,已有的屬性,好比name,不會在__getattr__中查找。
任何類,只須要定義一個__call__()方法,就能夠直接對實例進行調用。請看示例:
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)
調用方式以下:
>>> s = Student('Michael') >>> s() # self參數不要傳入 My name is Michael.
__call__()
還能夠定義參數。對實例進行直接調用就比如對一個函數進行調用同樣,因此你徹底能夠把對象當作函數,把函數當作對象,由於這二者之間原本就沒啥根本的區別。
若是你把對象當作函數,那麼函數自己其實也能夠在運行期動態建立出來,由於類的實例都是運行期建立出來的,這麼一來,咱們就模糊了對象和函數的界限。
那麼,怎麼判斷一個變量是對象仍是函數呢?其實,更多的時候,咱們須要判斷一個對象是否能被調用,能被調用的對象就是一個Callable對象,好比函數和咱們上面定義的帶有__call__()的類實例:
>>> callable(Student()) True >>> callable(max) True >>> callable([1, 2, 3]) False >>> callable(None) False >>> callable('str') False
經過callable()函數,咱們就能夠判斷一個對象是不是「可調用」對象。
當咱們須要定義常量時,一個辦法是用大寫變量經過整數來定義,例如月份:
JAN = 1 FEB = 2 MAR = 3 ... NOV = 11 DEC = 12
好處是簡單,缺點是類型是int,而且仍然是變量。
更好的方法是爲這樣的枚舉類型定義一個class類型,而後,每一個常量都是class的一個惟一實例。Python提供了Enum類來實現這個功能:
from enum import Enum Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
這樣咱們就得到了Month類型的枚舉類,能夠直接使用Month.Jan來引用一個常量,或者枚舉它的全部成員:
for name, member in Month.__members__.items(): print(name, '=>', member, ',', member.value)
value屬性則是自動賦給成員的int常量,默認從1開始計數。
若是須要更精確地控制枚舉類型,能夠從Enum派生出自定義類:
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
@unique
裝飾器能夠幫助咱們檢查保證沒有重複值。
Enum能夠把一組相關常量定義在一個class中,且class不可變,並且成員能夠直接比較。
class Hello(object): def hello(self, name='world'): print('Hello, %s.' % name)
當Python解釋器載入hello模塊時,就會依次執行該模塊的全部語句,執行結果就是動態建立出一個Hello的class對象,測試以下:
>>> from hello import Hello >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class 'hello.Hello'>
type()函數能夠查看一個類型或變量的類型,Hello是一個class,它的類型就是type,而h是一個實例,它的類型就是class Hello。
咱們說class的定義是運行時動態建立的,而建立class的方法就是使用type()函數。
type()函數既能夠返回一個對象的類型,又能夠建立出新的類型,好比,咱們能夠經過type()函數建立出Hello類,而無需經過class Hello(object)...的定義:
>>> def fn(self, name='world'): # 先定義函數 ... print('Hello, %s.' % name) ... >>> Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class >>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'>
要建立一個class對象,type()函數依次傳入3個參數:
class的名稱;
繼承的父類集合,注意Python支持多重繼承,若是隻有一個父類,別忘了tuple的單元素寫法;
class的方法名稱與函數綁定,這裏咱們把函數fn綁定到方法名hello上。
經過type()函數建立的類和直接寫class是徹底同樣的,由於Python解釋器遇到class定義時,僅僅是掃描一下class定義的語法,而後調用type()函數建立出class。
正常狀況下,咱們都用class Xxx...來定義類,可是,type()函數也容許咱們動態建立出類來,也就是說,動態語言自己支持運行期動態建立類
,** 這和靜態語言有很是大的不一樣 ** ,要在靜態語言運行期建立類,必須構造源代碼字符串再調用編譯器,或者藉助一些工具生成字節碼實現,本質上都是動態編譯,會很是複雜。
metaclass,直譯爲元類,簡單的解釋就是:
當咱們定義了類之後,就能夠根據這個類建立出實例,因此:先定義類,而後建立實例。
可是若是咱們想建立出類呢?那就必須根據metaclass建立出類,因此:先定義metaclass,而後建立類。
鏈接起來就是:先定義metaclass,就能夠建立類,最後建立實例。
因此,metaclass容許你建立類或者修改類。換句話說,你能夠把類當作是metaclass建立出來的「實例」。
metaclass是Python面向對象裏最難理解,也是最難使用的魔術代碼。正常狀況下,你不會碰到須要使用metaclass的狀況,因此,如下內容看不懂也不要緊,由於基本上你不會用到。
- 更多內容參考:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319106919344c4ef8b1e04c48778bb45796e0335839000
try...except...finally...
try
try: print('try...') r = 10 / 0 print('result:', r) except ZeroDivisionError as e: print('except:', e) finally: print('finally...') print('END')
finally
若是有,則必定會被執行(能夠沒有finally語句)
不須要在每一個可能出錯的地方去捕獲錯誤,只要在合適的層次去捕獲錯誤就能夠了。這樣一來,就大大減小了寫try...except...finally的麻煩。
若是錯誤沒有被捕獲,它就會一直往上拋,最後被Python解釋器捕獲,打印一個錯誤信息,而後程序退出。
若是不捕獲錯誤,天然可讓Python解釋器來打印出錯誤堆棧,但程序也被結束了。既然咱們能捕獲錯誤,就能夠把錯誤堆棧打印出來,而後分析錯誤緣由,同時,讓程序繼續執行下去。
Python內置的logging模塊能夠很是容易地記錄錯誤信息:
# err_logging.py import logging def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('END')
經過配置,logging還能夠把錯誤記錄到日誌文件裏,方便過後排查。
raise
語句)若是要拋出錯誤,首先根據須要,能夠定義一個錯誤的class,選擇好繼承關係,而後,用raise語句拋出一個錯誤的實例:
# err_raise.py class FooError(ValueError): pass def foo(s): n = int(s) if n==0: raise FooError('invalid value: %s' % s) return 10 / n foo('0')
執行,能夠最後跟蹤到咱們本身定義的錯誤:
$ python3 err_raise.py Traceback (most recent call last): File "err_throw.py", line 11, in <module> foo('0') File "err_throw.py", line 8, in foo raise FooError('invalid value: %s' % s) __main__.FooError: invalid value: 0
Python內置的try...except...finally
用來處理錯誤十分方便。出錯時,會分析錯誤信息並定位錯誤發生的代碼位置纔是最關鍵的。
程序也能夠主動拋出錯誤,讓調用者來處理相應的錯誤。可是,應該在文檔中寫清楚可能會拋出哪些錯誤,以及錯誤產生的緣由。
logging容許你指定記錄信息的級別,有debug
,info
,warning
,error
等幾個級別,當咱們指定level=INFO時,logging.debug就不起做用了。同理,指定level=WARNING後,debug和info就不起做用了。這樣一來,你能夠放心地輸出不一樣級別的信息,也不用刪除,最後統一控制輸出哪一個級別的信息。
logging的另外一個好處是經過簡單的配置,一條語句能夠同時輸出到不一樣的地方,好比console和文件。
pdb
第4種方式是啓動Python的調試器pdb,讓程序以單步方式運行
pdb.set_trace()
這個方法也是用pdb,可是不須要單步執行,咱們只須要import pdb,而後,在可能出錯的地方放一個pdb.set_trace(),就能夠設置一個斷點:
# err.py import pdb s = '0' n = int(s) pdb.set_trace() # 運行到這裏會自動暫停 print(10 / n)
自動執行寫在註釋中的這些代碼。Python內置的「文檔測試」(doctest)模塊能夠直接提取註釋中的代碼並執行測試。
讓咱們用doctest來測試上次編寫的Dict類:
# mydict2.py class Dict(dict): ''' Simple dict but also support access as x.y style. >>> d1 = Dict() >>> d1['x'] = 100 >>> d1.x 100 >>> d1.y = 200 >>> d1['y'] 200 >>> d2 = Dict(a=1, b=2, c='3') >>> d2.c '3' >>> d2['empty'] Traceback (most recent call last): ... KeyError: 'empty' >>> d2.empty Traceback (most recent call last): ... AttributeError: 'Dict' object has no attribute 'empty' ''' def __init__(self, **kw): super(Dict, self).__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value if __name__=='__main__': import doctest doctest.testmod()
運行python3 mydict2.py:
$ python3 mydict2.py
try: f = open('/path/to/file', 'r') print(f.read()) finally: if f: f.close()
可是每次都這麼寫實在太繁瑣,因此,Python引入了with
語句來自動幫咱們調用close()
方法:
with open('/path/to/file', 'r') as f: print(f.read())
調用read()會一次性讀取文件的所有內容,若是文件有10G,內存就爆了,因此,要保險起見,能夠反覆調用read(size)方法,每次最多讀取size個字節的內容。另外,調用readline()能夠每次讀取一行內容,調用readlines()一次讀取全部內容並按行返回list。所以,要根據須要決定怎麼調用。
若是文件很小,read()一次性讀取最方便;若是不能肯定文件大小,反覆調用read(size)比較保險;若是是配置文件,調用readlines()最方便:
for line in f.readlines(): print(line.strip()) # 把末尾的'\n'刪掉
像open()函數返回的這種有個read()方法的對象,在Python中統稱爲file-like Object。除了file外,還能夠是內存的字節流,網絡流,自定義流等等。file-like Object不要求從特定類繼承,只要寫個read()方法就行。
'rb'
模式打開文件便可:>>> f = open('/Users/michael/test.jpg', 'rb') >>> f.read() b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六進制表示的字節
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk') >>> f.read() '測試'
遇到有些編碼不規範的文件,你可能會遇到UnicodeDecodeError,由於在文本文件中可能夾雜了一些非法編碼的字符。遇到這種狀況,open()函數還接收一個errors參數,表示若是遇到編碼錯誤後如何處理。最簡單的方式是直接忽略:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
'w'
或者'wb'
表示寫文本文件或寫二進制文件:>>> f = open('/Users/michael/test.txt', 'w') >>> f.write('Hello, world!') >>> f.close()
你能夠反覆調用write()來寫入文件,可是務必要調用f.close()來關閉文件。當咱們寫文件時,操做系統每每不會馬上把數據寫入磁盤,而是放到內存緩存起來,空閒的時候再慢慢寫入。只有調用close()方法時,操做系統才保證把沒有寫入的數據所有寫入磁盤。忘記調用close()的後果是數據可能只寫了一部分到磁盤,剩下的丟失了。因此,仍是用with語句來得保險:
with open('/Users/michael/test.txt', 'w') as f: f.write('Hello, world!')
要寫入特定編碼的文本文件,請給open()函數傳入encoding參數,將字符串自動轉換成指定編碼。
>>> from io import StringIO >>> f = StringIO() >>> f.write('hello') 5 >>> f.write(' ') 1 >>> f.write('world!') 6 >>> print(f.getvalue()) hello world!
getvalue()
方法用於得到寫入後的str。
要讀取StringIO,能夠用一個str初始化StringIO,而後,像讀文件同樣讀取:
>>> from io import StringIO >>> f = StringIO('Hello!\nHi!\nGoodbye!') >>> while True: ... s = f.readline() ... if s == '': ... break ... print(s.strip()) ... Hello! Hi! Goodbye!
>>> from io import BytesIO >>> f = BytesIO() >>> f.write('中文'.encode('utf-8')) 6 >>> print(f.getvalue()) b'\xe4\xb8\xad\xe6\x96\x87'
請注意,寫入的不是str,而是通過UTF-8編碼的bytes。
和StringIO相似,能夠用一個bytes初始化BytesIO,而後,像讀文件同樣讀取:
>>> from io import StringIO >>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87') >>> f.read() b'\xe4\xb8\xad\xe6\x96\x87'
Python內置的os模塊也能夠直接調用操做系統提供的接口函數。
>>> os.environ environ({'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'TERM_PROGRAM_VERSION': '326', 'LOGNAME': 'michael', 'USER': 'michael', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin', ...}) >>> os.environ.get('PATH') '/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin' >>> os.environ.get('x', 'default') 'default'
# 查看當前目錄的絕對路徑: >>> os.path.abspath('.') '/Users/michael' # 在某個目錄下建立一個新目錄,首先把新目錄的完整路徑表示出來: >>> os.path.join('/Users/michael', 'testdir') '/Users/michael/testdir' # 而後建立一個目錄: >>> os.mkdir('/Users/michael/testdir') # 刪掉一個目錄: >>> os.rmdir('/Users/michael/testdir')
Python的os模塊封裝了操做系統的目錄和文件操做,要注意這些函數有的在os模塊中,有的在os.path模塊中。
咱們把變量從內存中變成可存儲或傳輸的過程稱之爲序列化,在Python中叫pickling
,在其餘語言中也被稱之爲serialization,marshalling,flattening等等,都是一個意思。
序列化以後,就能夠把序列化後的內容寫入磁盤,或者經過網絡傳輸到別的機器上。
反過來,把變量內容從序列化的對象從新讀到內存裏稱之爲反序列化,即unpickling
。
Python提供了pickle
模塊來實現序列化。
pickle.dumps()方法把任意對象序列化成一個bytes,而後,就能夠把這個bytes寫入文件。或者用另外一個方法pickle.dump()直接把對象序列化後寫入一個file-like Object:
>>> f = open('dump.txt', 'wb') >>> pickle.dump(d, f) >>> f.close()
看看寫入的dump.txt文件,一堆亂七八糟的內容,這些都是Python保存的對象內部信息。
當咱們要把對象從磁盤讀到內存時,能夠先把內容讀到一個bytes,而後用pickle.loads()方法反序列化出對象,也能夠直接用pickle.load()方法從一個file-like Object中直接反序列化出對象。咱們打開另外一個Python命令行來反序列化剛纔保存的對象:
>>> f = open('dump.txt', 'rb') >>> d = pickle.load(f) >>> f.close() >>> d {'age': 20, 'score': 88, 'name': 'Bob'}
- Pickle的問題和全部其餘編程語言特有的序列化問題同樣,就是它只能用於Python,而且可能不一樣版本的Python彼此都不兼容,所以,只能用Pickle保存那些不重要的數據,不能成功地反序列化也不要緊。
>>> import json >>> d = dict(name='Bob', age=20, score=88) >>> json.dumps(d) '{"age": 20, "score": 88, "name": "Bob"}'
dumps()方法返回一個str,內容就是標準的JSON。相似的,dump()方法能夠直接把JSON寫入一個file-like Object。
可選參數default就是把任意一個對象變成一個可序列爲JSON的對象,咱們只須要爲Student專門寫一個轉換函數,再把函數傳進去便可:
def student2dict(std): return { 'name': std.name, 'age': std.age, 'score': std.score }
這樣,Student實例首先被student2dict()函數轉換成dict,而後再被順利序列化爲JSON:
>>> print(json.dumps(s, default=student2dict)) {"age": 20, "name": "Bob", "score": 88}
不過,下次若是遇到一個Teacher類的實例,照樣沒法序列化爲JSON。咱們能夠偷個懶,把任意class的實例變爲dict:
print(json.dumps(s, default=lambda obj: obj.__dict__))
略
在正則表達式中,若是直接給出字符,就是精確匹配。用\d能夠匹配一個數字,\w能夠匹配一個字母或數字,因此:
'00\d'
能夠匹配'007'
,但沒法匹配'00A'
;
'\d\d\d'
能夠匹配'010'
;
'\w\w\d'
能夠匹配'py3'
;
.
能夠匹配任意字符,因此:
'py.'
能夠匹配'pyc'
、'pyo'
、'py!'
等等。
要匹配變長的字符,在正則表達式中,用*
表示任意個字符(包括0個),用+
表示至少一個字符,用?
表示0個或1個字符,用{n}
表示n個字符,用{n,m}
表示n-m個字符
[0-9a-zA-Z\_]
能夠匹配一個數字、字母或者下劃線;
[0-9a-zA-Z\_]+
能夠匹配至少由一個數字、字母或者下劃線組成的字符串,好比'a100','0_Z','Py3000'等等;
[a-zA-Z\_][0-9a-zA-Z\_]*
能夠匹配由字母或下劃線開頭,後接任意個由一個數字、字母或者下劃線組成的字符串,也就是Python合法的變量;
[a-zA-Z\_][0-9a-zA-Z\_]{0, 19}
更精確地限制了變量的長度是1-20個字符(前面1個字符+後面最多19個字符)。
A|B
能夠匹配A或B,因此(P|p)ython能夠匹配'Python'或者'python'。
^
表示行的開頭,^\d
表示必須以數字開頭。
$
表示行的結束,\d$
表示必須以數字結束。
你可能注意到了,py也能夠匹配'python',可是加上^py$
就變成了整行匹配,就只能匹配'py'了。
有了準備知識,咱們就能夠在Python中使用正則表達式了。Python提供re模塊,包含全部正則表達式的功能。因爲Python的字符串自己也用\
轉義,因此要特別注意:
match()
方法判斷是否匹配,若是匹配成功,返回一個Match對象
,不然返回None
。常見的判斷方法就是:
test = '用戶輸入的字符串' if re.match(r'正則表達式', test): print('ok') else: print('failed')
>>> 'a b c'.split(' ') ['a', 'b', '', '', 'c']
嗯,沒法識別連續的空格,用正則表達式試試:
>>> re.split(r'\s+', 'a b c') ['a', 'b', 'c']
不管多少個空格均可以正常分割。加入,試試:
>>> 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{3})-(\d{3,8})$
分別定義了兩個組,能夠直接從匹配的字符串中提取出區號和本地號碼:
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345') >>> m <_sre.SRE_Match object; span=(0, 9), match='010-12345'> >>> m.group(0) '010-12345' >>> m.group(1) '010' >>> m.group(2) '12345'
** 注意到group(0)永遠是原始字符串,group(1)、group(2)……表示第一、二、……個子串。 **
最後須要特別指出的是,正則匹配默認是貪婪匹配
,也就是匹配儘量多的字符。舉例以下,匹配出數字後面的0:
>>> re.match(r'^(\d+)(0*)$', '102300').groups() ('102300', '')
因爲\d+
採用貪婪匹配,直接把後面的0
所有匹配了,結果0*
只能匹配空字符串了。
必須讓\d+
採用非貪婪匹配(也就是儘量少匹配),才能把後面的0匹配出來,加個?就可讓\d+採用非貪婪匹配:
>>> re.match(r'^(\d+?)(0*)$', '102300').groups() ('1023', '00')
當咱們在Python中使用正則表達式時,re模塊內部會幹兩件事情:
編譯正則表達式,若是正則表達式的字符串自己不合法,會報錯;
用編譯後的正則表達式去匹配字符串。
若是一個正則表達式要重複使用幾千次,出於效率的考慮,咱們能夠預編譯該正則表達式,接下來重複使用時就不須要編譯這個步驟了,直接匹配:
>>> import re # 編譯: >>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$') # 使用: >>> re_telephone.match('010-12345').groups() ('010', '12345') >>> re_telephone.match('010-8086').groups() ('010', '8086')
編譯後生成Regular Expression對象,因爲該對象本身包含了正則表達式,因此調用對應的方法時不用給出正則字符串。
PIL:Python Imaging Library,已是Python平臺事實上的圖像處理標準庫了。PIL功能很是強大,但API卻很是簡單易用。
安裝Pillow
在命令行下直接經過pip安裝:
$ pip install pillow
若是遇到Permission denied安裝失敗,請加上sudo重試。
操做圖像
來看看最多見的圖像縮放操做,只需三四行代碼:
from PIL import Image # 打開一個jpg圖像文件,注意是當前路徑: im = Image.open('test.jpg') # 得到圖像尺寸: w, h = im.size print('Original image size: %sx%s' % (w, h)) # 縮放到50%: im.thumbnail((w//2, h//2)) print('Resize image to: %sx%s' % (w//2, h//2)) # 把縮放後的圖像用jpeg格式保存: im.save('thumbnail.jpg', 'jpeg')
其餘功能如切片、旋轉、濾鏡、輸出文字、調色板等包羅萬象。
好比,模糊效果也只需幾行代碼:
from PIL import Image, ImageFilter # 打開一個jpg圖像文件,注意是當前路徑: im = Image.open('test.jpg') # 應用模糊濾鏡: im2 = im.filter(ImageFilter.BLUR) im2.save('blur.jpg', 'jpeg')
效果以下:
PIL-blur
PIL的ImageDraw提供了一系列繪圖方法,讓咱們能夠直接繪圖。好比要生成字母驗證碼圖片:
from PIL import Image, ImageDraw, ImageFont, ImageFilter import random # 隨機字母: def rndChar(): return chr(random.randint(65, 90)) # 隨機顏色1: def rndColor(): return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255)) # 隨機顏色2: def rndColor2(): return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127)) # 240 x 60: width = 60 * 4 height = 60 image = Image.new('RGB', (width, height), (255, 255, 255)) # 建立Font對象: font = ImageFont.truetype('Arial.ttf', 36) # 建立Draw對象: draw = ImageDraw.Draw(image) # 填充每一個像素: for x in range(width): for y in range(height): draw.point((x, y), fill=rndColor()) # 輸出文字: for t in range(4): draw.text((60 * t + 10, 10), rndChar(), font=font, fill=rndColor2()) # 模糊: image = image.filter(ImageFilter.BLUR) image.save('code.jpg', 'jpeg')
若是運行的時候報錯:
IOError: cannot open resource
這是由於PIL沒法定位到字體文件的位置,能夠根據操做系統提供絕對路徑,好比:
'/Library/Fonts/Arial.ttf'
要詳細瞭解PIL的強大功能,請請參考Pillow官方文檔:
https://pillow.readthedocs.org/
小結
PIL提供了操做圖像的強大功能,能夠經過簡單的代碼完成複雜的圖像處理。
每一個應用可能須要各自擁有一套「獨立」的Python運行環境。virtualenv就是用來爲一個應用建立一套「隔離」的Python運行環境。
$ pip3 install virtualenv
Mac:~ michael$ mkdir myproject Mac:~ michael$ cd myproject/ Mac:myproject michael$
venv
,(virtualenv --no-site-packages venv
):Mac:myproject michael$ virtualenv --no-site-packages venv Using base prefix '/usr/local/.../Python.framework/Versions/3.4' New python executable in venv/bin/python3.4 Also creating executable in venv/bin/python Installing setuptools, pip, wheel...done.
命令virtualenv
就能夠建立一個獨立的Python運行環境,咱們還加上了參數--no-site-packages
,這樣,已經安裝到系統Python環境中的全部第三方包都不會複製過來,這樣,咱們就獲得了一個不帶任何第三方包的「乾淨」的Python運行環境。
source venv/bin/activate
)Mac:myproject michael$ source venv/bin/activate (venv)Mac:myproject michael$
注意到命令提示符變了,有個(venv)前綴,表示當前環境是一個名爲venv的Python環境。
** virtualenv爲應用提供了隔離的Python運行環境,解決了不一樣應用間多版本的衝突問題。 **
# 導入SQLite驅動: >>> import sqlite3 # 鏈接到SQLite數據庫 # 數據庫文件是test.db # 若是文件不存在,會自動在當前目錄建立: >>> conn = sqlite3.connect('test.db') # 建立一個Cursor: >>> cursor = conn.cursor() # 執行一條SQL語句,建立user表: >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))') <sqlite3.Cursor object at 0x10f8aa260> # 繼續執行一條SQL語句,插入一條記錄: >>> cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')') <sqlite3.Cursor object at 0x10f8aa260> # 經過rowcount得到插入的行數: >>> cursor.rowcount 1 # 關閉Cursor: >>> cursor.close() # 提交事務: >>> conn.commit() # 關閉Connection: >>> conn.close()
MySQL的配置文件默認存放在/etc/my.cnf或者/etc/mysql/my.cnf,設置默認編碼:
[client] default-character-set = utf8 [mysqld] default-storage-engine = INNODB character-set-server = utf8 collation-server = utf8_general_ci
$ pip install mysql-connector-python --allow-external mysql-connector-python
MySQL服務器的test數據庫
# 導入MySQL驅動: >>> import mysql.connector # 注意把password設爲你的root口令: >>> conn = mysql.connector.connect(user='root', password='password', database='test') >>> cursor = conn.cursor() # 建立user表: >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))') # 插入一行記錄,注意MySQL的佔位符是%s: >>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael']) >>> cursor.rowcount 1 # 提交事務: >>> conn.commit() >>> cursor.close() # 運行查詢: >>> cursor = conn.cursor() >>> cursor.execute('select * from user where id = %s', ('1',)) >>> values = cursor.fetchall() >>> values [('1', 'Michael')] # 關閉Cursor和Connection: >>> cursor.close() True >>> conn.close()
- Flask,比較流行的Web框架
- Django:全能型Web框架;
- web.py:一個小巧的Web框架;
- Bottle:和Flask相似的Web框架;
- Tornado:Facebook的開源異步Web框架。
Flask經過render_template()
函數來實現模板的渲染。和Web框架相似,Python的模板也有不少種。Flask默認支持的模板是jinja2
。
$ pip install jinja2
在Jinja2模板中,咱們用{{ name }}
表示一個須要替換的變量。不少時候,還須要循環、條件判斷等指令語句,在Jinja2中,用{% ... %}
表示指令。
除了Jinja2,常見的模板還有:
- Mako:用<% ... %>和${xxx}的一個模板;
- Cheetah:也是用<% ... %>和${xxx}的一個模板;
- Django:Django是一站式框架,內置一個用{% ... %}和{{ xxx }}的模板。
因此子程序調用是經過棧實現的,一個線程就是執行一個子程序。
子程序調用老是一個入口,一次返回,調用順序是明確的。而協程的調用和子程序不一樣。
協程看上去也是子程序,但執行過程當中,在子程序內部可中斷,而後轉而執行別的子程序,在適當的時候再返回來接着執行。
** 和多線程比,協程有何優點? **
- 最大的優點就是協程極高的執行效率。由於子程序切換不是線程切換,而是由程序自身控制,所以,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優點就越明顯。
- 第二大優點就是不須要多線程的鎖機制,由於只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只須要判斷狀態就行了,因此執行效率比多線程高不少。
- 由於協程是一個線程執行,那怎麼利用多核CPU呢?最簡單的方法是多進程+協程,既充分利用多核,又充分發揮協程的高效率,可得到極高的性能。
Python對協程的支持是經過generator實現的。
在generator中,咱們不但能夠經過for循環來迭代,還能夠不斷調用next()函數獲取由yield語句返回的下一個值。
可是Python的yield不但能夠返回一個值,它還能夠接收調用者發出的參數。
asyncio
是Python 3.4版本引入的標準庫,直接內置了對異步IO的支持。
asyncio
的編程模型就是一個消息循環。咱們從asyncio
模塊中直接獲取一個EventLoop
的引用,而後把須要執行的協程扔到EventLoop
中執行,就實現了異步IO。
asyncio提供了完善的異步IO支持;
異步操做須要在coroutine
中經過yield from
完成;
多個coroutine
能夠封裝成一組Task而後併發執行。
用asyncio提供的@asyncio.coroutine能夠把一個generator標記爲coroutine類型,而後在coroutine內部用yield from調用另外一個coroutine實現異步操做。
爲了簡化並更好地標識異步IO,從Python 3.5開始引入了新的語法async和await,可讓coroutine的代碼更簡潔易讀。
請注意,async和await是針對coroutine的新語法,要使用新的語法,只須要作兩步簡單的替換:
把@asyncio.coroutine
替換爲async
;
把yield from
替換爲await
。
讓咱們對比一下上一節的代碼:
@asyncio.coroutine def hello(): print("Hello world!") r = yield from asyncio.sleep(1) print("Hello again!")
用新語法從新編寫以下:
async def hello(): print("Hello world!") r = await asyncio.sleep(1) print("Hello again!")
剩下的代碼保持不變。
小結
Python從3.5版本開始爲asyncio提供了async和await的新語法;
注意新語法只能用在Python 3.5以及後續版本,若是使用3.4版本,則仍需使用上一節的方案。
asyncio能夠實現單線程併發IO操做。若是僅用在客戶端,發揮的威力不大。若是把asyncio用在服務器端,例如Web服務器,因爲HTTP鏈接就是IO操做,所以能夠用單線程+coroutine實現多用戶的高併發支持。
asyncio實現了TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的HTTP框架。
咱們先安裝aiohttp:
pip install aiohttp
而後編寫一個HTTP服務器,分別處理如下URL:
/
- 首頁返回b'<h1>Index</h1>'
;
/hello/{name}
- 根據URL參數返回文本hello, %s!
。
代碼以下:
import asyncio from aiohttp import web async def index(request): await asyncio.sleep(0.5) return web.Response(body=b'<h1>Index</h1>') 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')) async def init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', index) app.router.add_route('GET', '/hello/{name}', hello) srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000) print('Server started at http://127.0.0.1:8000...') return srv loop = asyncio.get_event_loop() loop.run_until_complete(init(loop)) loop.run_forever()
注意aiohttp的初始化函數init()也是一個coroutine,loop.create_server()則利用asyncio建立TCP服務。