人生苦短我用 Pythonjava
注:最後附電子書地址node
PEP 8:http://www.python.org/dev/peps/pep-0008/python
PEP 8:http://www.python.org/dev/peps/pep-0008/git
self
做爲其第一個參數。且self
也是對當前類對象的引用。cls
來做爲其第一個參數。且self
引用自當前類。if a is not b
) 而不是顯示的表達式(如if not a is b
)。if len(somelist) == 0
)來判斷空值。使用隱式的方式如來假設空值的狀況(如if not somelist
與 False
來進行比較)。[1]
,或者'hi')。對這些非空值而言 if somelist
默認包含隱式的True
。if
, for
, while
, except
等包含多個語塊的表達式寫在一行內,應該分割成多行。import
語句寫在Python
文件的頂部。bar
包下面的foo
模塊,應該使用from bar import foo
而不是import foo
。from . import foo
。 Python3
中的str
實例和Python2
中的unicode
實例並無相關聯的二進制編碼。因此要想將Unicode
字符轉換成二進制數據,就必須使用encode
方法,反過來,要想把二進制數據轉換成Unicode
字符,就必須使用decode
方法。github
當你開始寫Python
程序的時候,在接口的最開始位置聲明對Unicode
的編碼解碼的細節很重要。在你的代碼中,最核心的部分應使用Unicode
字符類型(Python3
中使用str
,Python2
中使用unicode
)而且不該該考慮關於字符編碼的任何其餘方式。本文容許你使用本身喜歡的可替代性的文本編碼方式(如Latin-1
,Shift JIS
, Big5
),可是應該對你的文本輸出編碼嚴格的限定一下(理想的方式是使用UTF-8
編碼)。算法
因爲字符類型的不一樣,致使了Python代碼中出現了兩種常見的情形的發生。shell
你想操做UTF-8
(或者其餘的編碼方式)編碼的8比特值 序列。express
你想操做沒有特定編碼的Unicode
字符。 因此你一般會須要兩個工具函數來對這兩種狀況的字符進行轉換,以此來確保輸入值符合代碼所預期的字符類型。編程
二進制值和unicode字符須要通過encode和decode轉換,Python2的unicode和Python3的str沒有關聯二進制編碼,一般使用UTF-8json
Python2轉換函數:
to_unicode
# Python 2
def to_unicode(unicode_or_str):
if isinstance(unicode_or_str, str):
value = unicode_or_str.decode('utf-8')
else:
value = unicode_or_str
return value # Instance of unicode
複製代碼
to_str
# Python 2
def to_str(unicode_or_str):
if isinstance(unicode_or_str, unicode):
value = unicode_or_str.encode('utf-8')
else:
value = unicode_or_str
return value # Instance of str
複製代碼
Python2,若是str只包含7-bit的ascii字符,unicode和str是同樣的類型,因此:
注:在Python2中,若是隻處理7位ASCII的情形下,能夠等價 str 和 unicode 上面的規則,在Python3中 bytes 和 str 實例毫不等價
使用open返回的文件操做,在Python3是默認進行UTF-8編碼,但在Pyhton2是二進制編碼
# python3
with open(‘/tmp/random.bin’, ‘w’) as f:
f.write(os.urandom(10))
# >>>
#TypeError: must be str, not bytes
複製代碼
這時咱們能夠用二進制方式進行寫入和讀取:
# python3
with open('/tmp/random.bin','wb) as f:
f.write(os.urandom(10))
複製代碼
分片機制自動處理越界問題,可是最好在表達邊界大小範圍是更加的清晰。(如a[:20]
或者a[-20:]
)
list,str,bytes和實現__getitem__和__setitem__ 這兩個特殊方法的類都支持slice操做
基本形式是:somelist[start:end],不包括end,可使用負數,-1 表示最後一個,默認正向選取,下標0能夠省略,最後一個下標也能夠省略
a = ['a','b','c','d','e','f','g','h']
print('Middle Two:',a[3:-3])
>>>
Middle Two: ['d','e']
複製代碼
slice list是shadow copy,somelist[0:]會複製原list,切割以後對新獲得的列表進行修改不會影響原來的列表
a = ['a','b','c','d','e','f','g','h']
b = a[4:]
print("Before:", b)
b[1] = 99
print("After:",b)
print("Original:",a)
>>>
Before: ['e','f','g','h']
After: ['e',99,'g','h']
Original: ['a','b','c','d','e','f','g','h']
複製代碼
slice賦值會修改slice list,即便長度不一致(增刪改)
print("Before:",a)
a[2:7] = [99,22,14]
print("After:",a)
>>>
Before: ['a','b','c','d','e','f','g','h']
After: ['a','b',99,22,14,'h']
複製代碼
引用-變化-追隨
當爲列表賦值的時候省去開頭和結尾下標的時候,將會用 這個引用 來替換整個列表的內容,而不是建立一個新的列表。同時,引用了這個列表的列表的相關內容,也會跟着發生變化。
a = ['a','b','c','d','e','f','g','h']
b = a
print("Before:",b)
a[:] = [101,102,103]
print("After:",b)
>>>
Before: ['a','b','c','d','e','f','g','h']
After: [101,102,103]
# 解決方案:深拷貝
import copy
b = copy.copy(a)
print("Before:",b)
a[:] = [101,102,103]
print("After:",b)
>>>
Before: ['a','b','c','d','e','f','g','h']
After: ['a','b','c','d','e','f','g','h']
複製代碼
start
,end
,stride
會讓人感到困惑,難於閱讀。start
,end
,stride
;若是非要使用,考慮兩次賦值(一個分片,一個調幅),或者使用內置模塊itertoolsde
的 islice
方法來進行處理。Python
有針對步幅的特殊的語法,形如:somelist[start:end:stride]
。
a = ['red','orange','yellow','green','blue','purple']
odds = a[::2]
print(odds)
>>>
['red','yellow','blue']
複製代碼
步幅爲-1來實現字符串的逆序,反向選取
# 當數據僅僅爲ASCII碼內數據時工做正常
x = b'mongoose'
y = x[::-1]
print(y)
>>>
b'esoognom'
# 出現Unicode字符的時候就會報錯
w = '謝謝'
x = w.encode(utf-8')
y = a[::-1]
z = y.decode('utf-8')
>>>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9d in position 0: invalid start byte.
a = ['a','b','c','d','e','f','g','h']
a[2::2] # ['c','e','g']
a[-2::-2] # ['g','e','c','a']
a[-2:2:-2] # ['g','e'] 尤爲注意這裏,相似於座標軸,分片範圍是左閉右開,因此2的位置不可達
a[2:2:-2] # []
複製代碼
map
,filter
更加清晰,由於map
,filter
須要額外的lambda
表達式的支持。map
沒有filter
幫助的話就不能完成這一個功能。第一個例子:
a = [1,2,3,4,5,6,7,8,9,10]
squares = [x*x for x in a]
print(squares)
>>>
[1,4,9,16,25,36,49,64,81,100]
複製代碼
map和filter須要lambda函數,使得代碼更不可讀
squares = map(lambda x: x **2 ,a)
複製代碼
第二個例子:
even_squares = [x**2 for x in a if x%2==0]
print(even_squares)
>>>
[4,16,36,64,100]
複製代碼
map:
alt = map(lambda x: x**2, filter(lambda x: x%2==0,a))
assert even_squares== list(alt)
複製代碼
字典和集合 有他們本身的一套列表表達式。這使得書寫算法的時候導出數據結構更加的簡單。
chile_rank = {'ghost':1,'habanero':2,'cayenne':3}
rank_dict = {rank:name for name,rank in child_rank.items()}
chile_len_set = {len(name) for name in rank_dict.values()}
print(rand_dict)
print(chile_len_set)
>>>
{1: 'ghost',2: 'habanero',3: 'cayenne'}
{8, 5, 7}
複製代碼
not:
squared = [[ x**2 for x in row] for row in matrix]
print(squared)
>>>
[[1, 4, 9],[16, 25, 36],[49, 64, 81]]
複製代碼
prefer:
matrix = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)
>>>
[ 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼
not:
my_lists = [
[[1, 2, 3],[4, 5, 6]],
# ...
]
flat = [ x for sublist in my_lists
for sublist2 in sublist
for x in sublist2]
print(flat)
複製代碼
prefer:
flat = []
for sublist in my_lists:
for sublist2 in sublist:
flat.append(sublist2)
複製代碼
從這點來看,多行的列表表達式並不比原方案少多少代碼。這裏,做者更加的建議使用正常的循環體語句。由於其比列表表達式更簡潔好看一點,也更加易讀,易懂。
列表表達式一樣支持if條件語句。多個條件語句出如今相同的循環水平中也是一個隱式&
的表達,即同時成立才成立。例如:你只想得到列表中大於4且是偶數的值。那麼下面的兩個列表表達式是等價的。
a = [1,2,3,4,5,6,7,8,9,10]
b = [x for x in a if x> 4 if x%2 ==0]
c = [x for x in a if x > 4 and if x%2 ==0]
複製代碼
條件語句能夠被很明確的添加在每一層循環的for
表達式的後面,起到過濾的做用。例如:你想過濾出每行總和大於10且能被3正處的元素。雖然用列表表達式表示出這段代碼很短,可是其可讀性確實很糟糕。
matrix = [[ 1, 2, 3],[ 4, 5, 6],[ 7, 8, 9]]
filtered = [[x for x in row if x%3==0]
for row in matrix if sum(row) >= 10 ]
print(filtered)
>>>
[[6],[9]]
複製代碼
列表生成式會給輸入列表中的每個只建立一個新的只包含一個元素的列表。這對於小的輸入序列多是很好用的,可是大的輸入序列而言就頗有可能致使你的程序崩潰。
Python
提供了一個generator expression
(生成器表達式),在程序運行的過程當中,生成其表達式不實現整個輸出序列,相反,生成其表達式僅僅是對從表達式中產生一個項目的迭代器進行計算,說白了就是每次僅僅處理一個迭代項,而不是整個序列。
生成器表達式經過使用相似於列表表達式的語法(在()
之間而不是[]
之間,僅此區別)來建立。
舉例:
it = ( len(x) for x in open('/tmp/my_file.txt'))
print(it)
>>>
<generator object <genexpr> at 0x101b81480>
print(next(it))
print(next(it))
>>>
100
57
複製代碼
鏈式操做:
roots = ((x,x**0.5) for x in it)
print(next(roots))
>>>
(15,3.872983346207417)
複製代碼
enumerate
提供了簡潔的語法,再循環迭代一個迭代器的同時既能獲取下標,也能獲取當前值。0
Prefer
for i, flavor in enumerate(flavor_list):
print(‘%d: %s’ % (i + 1, flavor))
複製代碼
not
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print(‘%d: %s’ % (i + 1, flavor))
# 也能夠經過指定 索引開始的下標序號來簡化代碼
for i, flavor in enumerate(flavor_list,1):
print("%d: %s"%(i,flavor))
複製代碼
zip
函數能夠並行的對多個迭代器進行處理。Python3
中,zip
採用懶模式生成器得到的是元組;而在Python2
中,zip
返回的是一個包含了其處理好的全部元祖的一個集合。zip
會默認截斷輸出,使得長度爲最早到達尾部的那個長度。itertools
中的zip_longest
函數能夠並行地處理多個迭代器,而能夠無視長度不一致的問題。Prefer:
# 求最長字符串
names = [‘Cecilia’, ‘Lise’, ‘Marie’]
max_letters = 0
letters = [len(n) for n in names]
for name, count in zip(names, letters):
if count > max_letters:
longest_name = name
max_letters = count
print(longest_name)
>>>
Cecilia
複製代碼
not:
for i, name in enumerate(names):
count = letters[i]
if count > max_letters:
longest_name = name
max_letters = count
複製代碼
Python
有用特殊的語法可以讓else
語塊在循環體結束的時候馬上獲得執行。else
語塊只有在循環體沒有觸發break
語句的時候纔會執行。else
語塊,由於這樣的表達不直觀,並且容易誤導讀者。for i in range(3):
print('Loop %d' % i)
else:
print('Else block')
>>>
Loop 0
Loop 1
Loop 2
Else block
複製代碼
try/finally
組合語句可使得你的代碼變得很整潔而無視try
塊中是否發生異常。else
塊能夠最大限度的減小try
塊中的代碼的長度,而且能夠可視化地辨別try/except
成功運行的部分。else
塊常常會被用於在try
塊成功運行後添加額外的行爲,可是要確保代碼會在finally
塊以前獲得運行。\finally 塊
老是會執行,能夠用來關閉文件句柄之類的
else 塊
try 塊沒有發生異常則執行 else 塊,有了 else 塊,咱們能夠儘可能減小 try 塊的代碼量
示例:
UNDEFINED = object()
def divide_json(path):
handle = open(path, 'r+') # May raise IOError
try:
data = handle.read() # May raise UnicodeDecodeError
op = json.loads(data) # May raise ValueError
value = (op['numerator'] / op['denominator']) # May raise ZeroDivisionError
except ZeroDivisionError as e:
return UNDEFINED
else:
op[‘result’] = value
result = json.dumps(op)
handle.seek(0)
handle.write(result) # May raise IOError
return value
finally:
handle.close() # Always runs
複製代碼
None
的函數來做爲特殊的含義是容易出錯的,由於None
和其餘的變量(例如 zero
,空字符串)在條件表達式的判斷情景下是等價的。None
是比較經常使用的一個方法。這樣調用方就可以合理地按照函數中的說明文檔來處理由此而引起的異常了。示例:
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
複製代碼
返回 None 容易形成誤用,下面的程式分不出 0 和 None
x, y = 0, 5
result = divide(x, y)
if not result:
print('Invalid inputs') # This is wrong!
else:
assert False
複製代碼
raise exception:
def divide(a, b):
try:
return a / b
except ZeroDivisionError as e:
raise ValueError('Invalid inputs') from e
複製代碼
調用者看到該函數的文檔中描述的異常以後,應該就會編寫相應的代碼來處理它們了。
x, y = 5, 2
try:
result = divide(x, y)
except ValueError:
print("Invalid inputs")
else:
print("Result is %.1f"% result)
>>>
Result is 2.5
複製代碼
Python3
中,可使用nonlocal
關鍵字來突破閉包的限制,進而在其檢索域內改變其值。(global
關鍵字用於使用全局變量,nonlocal
關鍵字用於使用局部變量(函數內))Python2
中沒有nonlocal
關鍵字,替代方案就是使用一個單元素(如列表,字典,集合等等)來實現與nonlocal
一致的功能。nonlocal
關鍵字。Python編譯器變量查找域的順序:
len
,str
等函數的域)考慮以下示例:
# 優先排序
def sort_priority2(values, group):
found = False # 做用域:sort_priority2
def helper(x):
if x in group:
found = True # 做用域: helper
return (0, x)
return (1, x) # found在helper的做用域就會由helper轉至sort_priority2函數
values.sort(key=helper)
return found
values = [1,5,3,9,7,4,2,8,6]
group = [7,9]
# begin to call
found = sort_priority2(values, group)
print("Found:",found)
print(values)
>>>
Found: False
[7, 9, 1, 2, 3, 4, 5, 6, 8]
複製代碼
排序的結果是正確的,可是很明顯分組的那個標誌是不正確的了。group
中的元素無疑能夠在values
裏面找到,可是函數卻返回了False
,爲何會發生這樣的情況呢?(提示:Python 編譯器變量查找域的順序)
在Python3
中,對於閉包而言有一個把數據放到外邊的特殊的語法。nonlocal
語句習慣於用來表示一個特定變量名稱的域的遍歷發生在賦值以前。 惟一的限制就是nonlocal
不會向上遍歷到模塊域級別(這也是爲了防止污染全局變量空間)。這裏,我定義了一個使用了nonlocal
關鍵字的函數。
def srt_priority3(numbers, group):
found = False
def helper(x):
nonlocal found
if x in group:
found = True
return (0, x)
return (1, x)
numbers.sort(key=helper)
return found
複製代碼
當數據在閉包外將被賦值到另外一個域時,nonlocal
語句使得這個過程變得很清晰。它也是對global
語句的一個補充,能夠明確的代表變量的賦值應該被直接放置到模塊域中。
然而,像這樣的反模式,對使用在那些簡單函數以外的其餘的任何地方。nonlocal
引發的反作用是難以追蹤的,而在那些包含着nonlocal
語句和賦值語句交叉聯繫的大段代碼的函數的內部則尤其明顯。
當你感受本身的nonlocal
語句開始變的複雜的時候,我很是建議你重構一下代碼,寫成一個工具類。這裏,我定義了一個實現了與上面的那個函數功能相一致的工具類。雖然有點長,可是代碼卻變得更加的清晰了(詳見第23項:對於簡單接口使用函數而不是類裏面的__call__
方法)。
class Sorter(object):
def __init__(self, group):
self.group = group
self.found = False
def __call__(self, x):
if x in self.group:
self.found = True
return (0, x)
return (1, x)
sorter = Sorter(group)
numbers.sort(key=sorter)
assert sorter is True
複製代碼
不幸的是,Python2
是不支持nonlocal
關鍵字的。爲了實現類似的功能,你須要普遍的藉助於Python
的做用與域規則。雖然這個方法並非完美的,可是這是Python
中比較經常使用的一種作法。
# Python2
def sort_priority(numbers, group):
found = [False]
def helper(x):
if x in group:
found[0] = True
return (0, x)
return (1, x)
numbers.sort(sort=helper)
return found[0]
複製代碼
就像上面解釋的那樣,Python
將會橫向查找該變量所在的域來分析其當前值。技巧就是發現的值是一個易變的列表。這意味着一旦檢索,閉包就能夠修改found
的狀態值,而且把內部數據的改變發送到外部,這也就打破了閉包引起的局部變量做用域沒法被改變的難題。其根本仍是在於列表自己元素值能夠被改變,這纔是此函數能夠正常工做的關鍵。
當found
爲一個dictionary
類型的時候,也是能夠正常工做的,原理與上文所言一致。此外,found
還能夠是一個集合,一個你自定義的類等等。
yield
變量的集合。考慮如下兩種版本代碼,一個用 **list **,另外一個用 generator
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result
address = 'Four score and seven years ago...'
result = index_words(address)
print(result[:3]) # [0, 5, 11]
複製代碼
generator
def index_words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1
result = list(index_words_iter(address))
複製代碼
使用 **generator ** 比較簡單,減小了 list 操做
另外一個 **generator **的好處是更有效率地使用記憶值,generator不須要有存所有的資料
import itertools
def index_file(handle):
offset = 0
for line in handle:
if line:
yield offset
for letter in line:
offset += 1
if letter == ' ':
yield offset
with open('/tmp/address.txt', 'r') as f:
it = index_file(f)
results = itertools.islice(it, 0, 3)
print(list(results))
>>>
[0, 5, 11]
複製代碼
無論address.txt 多大都能處理
Python
的iterator
協議定義了容器和迭代器在iter
和next
下對於循環和相關表達式的關係。__iter__
方法,你就能夠很容易的定義一個可迭代的容器類。iter
方法,你就能夠預先檢測一個值是否是迭代器而不是容器。兩次結果一致那就是迭代器,不然就是容器了。generator不能重用:
def read_visits(data_path):
with open(data_path,'r') as f:
for line in f:
yield int(line)
it = read_visits('tmp/my_numbers.txt')
print(list(it))
print(list(it)) # 這裏其實已經執行到頭了
>>>
[15, 35, 80]
[]
複製代碼
形成上述結果的緣由是 一個迭代器每次只處理它自己的數據。若是你遍歷一個迭代器或者生成器自己已經引起了一個StopIteration
的異常,你就不可能得到任何數據了。
每次調用都建立iterator避免上面list分配內存
def normalize_func(get_iter): # get_iter 是函數
total = sum(get_iter()) # New iterator
result = []
for value in get_iter(): # New iterator
percent = 100 * value / total
result.append(percent)
return result
percentages = normalize_func(lambda: read_visits(path))
複製代碼
for循環會調用內置iter函數,進而調用對象的__iter__方法,__iter__會返回iterator對象(實現__next__方法)
用iter函數檢測iterator:
def normalize_defensive(numbers):
if iter(numbers) is iter(numbers): # 是個迭代器,這樣很差
raise TypeError('Must supply a container')
total = sum(numbers)
result = []
for value in numbers:
percent = 100 * value / total
result.append(percent)
return result
visits = [15, 35, 80]
normalize_defensive(visits)
visits = ReadVIsitors(path)
normalize_defensive(visits)
# 可是若是輸入值不是一個容器類的話,就會引起異常了
it = iter(visits)
normalize_defensive(it)
>>>
TypeError: Must supply a container
複製代碼
*args
定義語句,函數能夠接收可變數量的位置參數。*
操做符來將序列中的元素做爲位置變量。*
操做符的生成器變量可能會引發程序的內存溢出,或者機器宕機。*args
的函數添加新的位置參數能夠產生難於發現的問題,應該謹慎使用。舉例:
def log(message, values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print('%s: %s' % (message, values_str))
log('My numbers are', [1, 2])
log('Hi there', [])
複製代碼
def log(message, *values):
if not values:
print(message)
else:
values_str = ', '.join(str(x) for x in values)
print('%s: %s' % (message, values_str))
log('My numbers are', 1, 2)
log('Hi there')
複製代碼
第二個就比第一個要更有彈性
不過傳入生成器的時候,由於變長參數在傳給函數的時候,總要先轉換爲元組,因此若是生成器迭代的數據很大的話,可能會致使程序崩潰
關鍵字參數的好處:
若是本來的函數以下
def flow_rate(weight_diff, time_diff, period=1):
return (weight_diff / time_diff) * period
複製代碼
若是後來函數修改了
def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1):
return ((weight_diff / units_per_kg) / time_diff) * period
複製代碼
那麼能夠以下使用
flow_per_second = flow_rate(weight_diff, time_diff)
flow_per_hour = flow_rate(weight_diff, time_diff, period=3600)
pounds_per_hour = flow_rate(weight_diff, time_diff, period=3600, units_per_kg=2.2)
pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2) # 不推薦
複製代碼
None
做爲關鍵字參數的默認值會有一個動態值。要在該函數的說明文檔中詳細的記錄一下。not:
def log(message, when=datetime.now()):
print(‘%s: %s’ % (when, message))
log(‘Hi there!’)
sleep(0.1)
log(‘Hi again!’)
>>>
2014-11-15 21:10:10.371432: Hi there!
2014-11-15 21:10:10.371432: Hi again!
複製代碼
prefer:
def log(message, when=None):
"""Log a message with a timestamp. Args: message: Message to print when: datetime of when the message occurred. Default to the present time. """
when = datetime.now() if when is None else when
print("%s: %s" %(when, message))
# 測試
log('Hi there!')
sleep(0.1)
log('Hi again!')
>>>
2014-11-15 21:10:10.472303: Hi there!
2014-11-15 21:10:10.473395: Hi again!
複製代碼
上述方法形成 when 第一次被賦值以後便不會再從新賦值
not:
def decode(data, default={}):
try:
return json.loads(data)
except ValueError:
return default
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
>>>
Foo: {'stuff': 5, 'meep': 1}
Bar: {'stuff': 5, 'meep': 1}
複製代碼
prefer:
def decode(data, default=None):
"""Load JSON data from string. Args: data: JSON data to be decoded. default: Value to return if decoding fails. Defaults to an empty dictionary. """
if default is None:
default = {}
try:
return json.loads(data)
except ValueError:
return default
# 如今測試一下
foo = decode('bad data')
foo['stuff'] = 5
bar = decode('also bad')
bar['meep'] = 1
print('Foo:', foo)
print('Bar:', bar)
>>>
Foo: {'stuff': 5}
Bar: {'meep': 1}
複製代碼
keyword-only
參數能夠強迫函數調用者提供關鍵字來賦值,這樣對於容易令人疑惑的函數參數頗有效,尤爲適用於接收多個布爾變量的狀況。Python3
中有明確的keyword-only
函數語法。Python2
中能夠經過**kwargs
模擬實現keyword-only
函數語法,而且人工的觸發TypeError
異常。keyword-only
在函數參數列表中的位置很重要,這點你們尤爲應該明白!下面的程式使用上不方便,由於容易忘記 ignore_overflow 和 ignore_zero_division 的順序
def safe_division(number, divisor, ignore_overflow, ignore_zero_division):
try:
return number / divisor
except OverflowError:
if ignore_overflow:
return 0
else:
raise
except ZeroDivisionError:
if ignore_zero_division:
return float('inf')
else:
raise
result = safe_division(1, 10**500, True, False)
result = safe_division(1, 0, False, True)
複製代碼
用 keyword 引數可解決此問題,在 Python 3 能夠宣告強制接收 keyword-only 參數。
下面定義的這個 safe_division_c 函數,帶有兩個只能以關鍵字形式來指定的參數。參數列表裏面的 * 號,標誌着位置參數就此終結,以後的那些參數,都只能以關鍵字的形式來指定
def safe_division_c(number, divisor, *, ignore_overflow=False, ignore_zero_division=False):
try:
return number / divisor
except OverflowError:
if ignore_overflow:
return 0
else:
raise
except ZeroDivisionError:
if ignore_zero_division:
return float('inf')
else:
raise
safe_division_c(1, 10**500, True, False)
>>>
TypeError: safe_division_c() takes 2 positional arguments but 4 were given
safe_division(1, 0, ignore_zero_division=True) # OK
...
複製代碼
Python 2 雖然沒有這種語法,但能夠用 **
操做符模擬
注:*
操做符接收可變數量的位置參數,**
接受任意數量的關鍵字參數
# Python 2
def safe_division(number, divisor, **kwargs):
ignore_overflow = kwargs.pop('ignore_overflow', False)
ignore_zero_division = kwargs.pop('ignore_zero_division', False)
if kwargs:
raise TypeError("Unexpected **kwargs: %r"%kwargs)
# ···
# 測試
safe_division(1, 10)
safe_division(1, 0, ignore_zero_division=True)
safe_division(1, 10**500, ignore_overflow=True)
# 而想經過位置參數賦值,就不會正常的運行了
safe_division(1, 0, False, True)
>>>
TypeError:safe_division() takes 2 positional arguments but 4 were given.
複製代碼
namedtuple
製做輕量,不易發生變化的容器。dictionaries 以及 tuples 拿來存簡單的資料很方便,可是當資料愈來愈複雜時,例如多層 dictionaries 或是 n-tuples,程式的可讀性就降低了。例以下面的程式:
class SimpleGradebook(object):
def __init__(self):
self._grades = {}
def add_student(self, name):
self._grades[name] = []
def report_grade(self, name, score):
self._grades[name].append(score)
def average_grade(self, name):
grades = self._grades[name]
return sum(grades) / len(grades)
複製代碼
正是因爲字典很容易被使用,以致於對字典過分的拓展會致使代碼愈來愈脆弱。例如:你想拓展一下SimpleGradebook
類來根據科目保存成績的學生的集合,而再也不是總體性的存儲。你就能夠經過修改_grade
字典來匹配學生姓名,使用另外一個字典來包含成績。而最裏面的這個字典將匹配科目(keys
)和成績(values
)。你還想根據班級內整體的成績來追蹤每一個門類分數所佔的比重,因此期中,期末考試相比於平時的測驗而言更爲重要。實現這個功能的一個方式是改變最內部的那個字典,而不是讓其關聯着科目(key
)和成績(values
)。咱們可使用元組(tuple
)來做爲成績(values
)。
class WeightedGradebook(object):
def __init__(self):
self._grades = {}
def add_student(self, name):
self._grades[name] = {}
def report_grade(self, name, subject, score, weight):
by_subject = self._grades[name]
grade_list = by_subject.setdefault(subject, [])
grade_list.append((score, weight))
def average_grade(self, name):
by_subject = self._grades[name]
score_sum, score_count = 0, 0
for subject, scores in by_subject.items():
subject_avg, total_weight = 0, 0
for score, weight in scores:
subject_avg += score * weight
total_weight += weight
score_sum += subject_avg / total_weight
score_count += 1
return score_sum / score_count
複製代碼
這個類使用起來貌似也變的超級複雜了,而且每一個位置參數表明了什麼意思也不明不白的。
你能夠從依賴樹的底端開始,將其劃分紅多個類:一個單獨的成績類好像對於如此一個簡單的信息權重太大了。一個元組,使用元組彷佛很合適,由於成績是不會改變的了,這恰好符合元組的特性。這裏,我使用一個元組(score
, weight
)來追蹤列表中的成績信息。
import collections
Grade = collections.namedtuple('Grade', ('score', 'weight'))
class Subject(object):
def __init__(self):
self._grades = []
def report_grade(self, score, weight):
self._grades.append(Grade(score, weight))
def average_grade(self):
total, total_weight = 0, 0
for grade in self._grades:
total += grade.score * grade.weight
total_weight += grade.weight
return total / total_weight
class Student(object):
def __init__(self):
self._subjects = {}
def subject(self, name):
if name not in self._subjects:
self._subjects[name] = Subject()
return self._subjects[name]
def average_grade(self):
total, count = 0, 0
for subject in self._subjects.values():
total += subject.average_grade()
count += 1
return total / count
class Gradebook(object):
def __init__(self):
self._students = {}
def student(self, name):
if name not in self._students:
self._students[name] = Student()
return self._students[name]
複製代碼
Python
中,不須要定義或實現什麼類,對於簡單接口組件而言,函數就足夠了。Python
中引用函數和方法的緣由就在於它們是first-class
,能夠直接的被運用在表達式中。__call__
容許你像調用函數同樣調用一個對象實例。__call__
方法的狀態閉包類哦(詳見第15
項:瞭解閉包是怎樣與變量做用域的聯繫)。Python
中的許多內置的API
都容許你經過向函數傳遞參數來自定義行爲。這些被API
使用的hooks
將會在它們運行的時候回調給你的代碼。例如:list
類型的排序方法中有一個可選的key
參數來決定排序過程當中每一個下標的值。這裏,我使用一個lambda
表達式做爲這個鍵鉤子,根據名字中字符的長度來爲這個集合排序。
names = ['Socrates', 'Archimedes', 'Plato', 'Aristotle']
names.sort(key=lambda x: len(x))
print(names)
>>>
['Plato', Socrates', 'Aristotle', 'Archimedes']
複製代碼
在其餘的編程語言中,你可能指望一個抽象類做爲這個hooks
。可是在Python
中,許多的hooks
都是些無狀態的有良好定義參數和返回值的函數。而對於hooks
而言,使用函數是很理想的。由於更容易藐視,相對於類而言定義起來也更加的簡單。函數能夠做爲鉤子來工做是由於Python
有first-class
函數:在編程的時候函數,方法能夠像其餘的變量值同樣被引用,或者被傳遞給其餘的函數。
Python
容許類來定義__call__
這個特殊的方法。它容許一個對象像被函數同樣來被調用。這樣的一個實例也引發了callable
這個內True
的事實。
current = {'green': 12, 'blue': 3}
incremetns = [
('red', 5),
('blue', 17),
('orange', 9)
]
class BetterCountMissing(object):
def __init__(self):
self.added = 0
def __call__(self):
self.added += 1
return 0
counter = BetterCountMissing()
counter()
assert callable(counter)
# 這裏我使用一個BetterCountMissing實例做爲defaultdict函數的默認的hook值來追蹤缺省值被添加的次數。
counter = BetterCountMissing()
result = defaultdict(counter, current)
for key, amount in increments:
result[key] += amount
assert counter.added == 2
複製代碼
Python
的每一個類只支持單個的構造方法,__init__
。@classmethod
能夠爲你的類定義可替代構造方法的方法。使用 @classmethod
起到多態的效果:一個對於分層良好的類樹中,不一樣類之間相同名稱的方法卻實現了不一樣的功能的體現。
下面的函數 generate_inputs() 不夠通常化,只能使用 PathInputData ,若是想使用其它 InputData 的子類,必須改變函數。
class InputData(object):
def read(self):
raise NotImplementedError
class PathInputData(InputData):
def __init__(self, path):
super().__init__()
self.path = path
def read(self):
return open(self.path).read()
def generate_inputs(data_dir):
for name in os.listdir(data_dir):
yield PathInputData(os.path.join(data_dir, name))
複製代碼
問題在於創建 InputData
子類的物件不夠通常化,若是你想要編寫另外一個 InputData
的子類就必須重寫 read
方法幸虧有 @classmethod
,能夠達到同樣的效果。
class GenericInputData(object):
def read(self):
raise NotImplementedError
@classmethod
def generate_inputs(cls, config):
raise NotImplementedError
class PathInputData(GenericInputData):
def __init__(self, path):
super().__init__()
self.path = path
def read(self):
return open(self.path).read()
@classmethod
def generate_inputs(cls, config):
data_dir = config['data_dir']
for name in os.listdir(data_dir):
yield cls(os.path.join(data_dir, name))
複製代碼
Python
的解決實例化次序問題的方法MRO
解決了菱形繼承中超類屢次被初始化的問題。super
來初始化父類。先看一個還行的例子:
class MyBaseClass(object):
def __init__(self, value):
self.value = value
class TimesTwo(object):
def __init__(self):
self.value *= 2
class PlusFive(object):
def __init__(self):
self.value += 5
# 多繼承實例,注意繼承的次序哦
class OneWay(MyBaseClass, TimesTwo, PlusFive):
def __init__(self, value):
MyBaseClass.__init__(self, value)
TimesTwo.__init__(self)
PlusFive.__init__(self)
foo = OneWay(5)
print("First ordering is ( 5 * 2 ) + 5 = ", foo.value)
>>>
First ordering is (5 * 2 ) + 2 = 15
複製代碼
不使用 **super() **在多重繼承時可能會形成意想不到的問題,下面的程式形成所謂的 **diamond inheritance **。
class MyBaseClass(object):
def __init__(self, value):
self.value = value
class TimesFive(MyBaseClass):
def __init__(self, value):
MyBaseClass.__init__(self, value)
self.value *= 5
class PlusTwo(MyBaseClass):
def __init__(self, value):
MyBaseClass.__init__(self, value)
self.value += 2
class ThisWay(TimesFive, PlusTwo):
def __init__(self, value):
TimesFive.__init__(self, value)
PlusTwo.__init__(self, value)
# 測試
foo = ThisWay(5)
print('Should be (5 * 5) + 2 = 27 but is', foo.value)
>>>
Should be (5 * 5) + 2 = 27 but is 7
複製代碼
注:foo.value 的值是 7 ,而不是 27。由於 PlusTwo.__init__(self, value)
將值重設爲 5 了。
使用 super()
能夠正確獲得 27
# 如今,菱形繼承的超類,也就是最頂上的那個`MyBaseClass`只會被初始化一次,而其餘的兩個父類會按照被聲明的順序來初始化了。
class GoodWay(TimesFiveCorrect, PlusTwoCorrect):# Python 2
class MyBaseClass(object):
def __init__(self, value):
self.value = value
class TimesFiveCorrect(MyBaseClass):
def __init__(self, value):
super(TimesFiveCorrect, self).__init__(value)
self.value *= 5
class PlusTwoCorrect(MyBaseClass):
def __init__(self, value):
super(PlusTwoCorrect, self).__init__(value)
self.value += 2
class GoodWay(PlusTwoCorrect, TimesFiveCorrect):
def __init__(self, value):
super(GoodWay, self).__init__(value)
foo = GoodWay(5)
print("Should be 5 * (5 + 2) = 35 and is " , foo.value)
>>>
Should be 5 * (5 + 2) = 35 and is 35
複製代碼
python中父類實例化的規則是按照MRO
標準來進行的,MRO 的執行順序是 DFS
# Python 2
from pprint import pprint
pprint(GoodWay.mro())
>>>
[<class '__main__.GoodWay'>,
<class '__main__.TimesFiveCorrect'>,
<class '__main__.PlusTwoCorrect'>,
<class '__main__.MyBaseClass'>,
<class 'object'>]
複製代碼
最開始初始化GoodWay
的時候,程序並無真正的執行,而是走到這條繼承樹的樹根,從樹根往下才會進行初始化。因而咱們會先初始化MyBaseClass
的value
爲5
,而後是PlusTwoCorrect
的value
會變成7
,接着TimesFiveCorrect
的value
就天然的變成35
了。
Python 3 簡化了 **super() **的使用方式
class Implicit(MyBaseClass):
def __init__(self, value):
super().__init__(value * 2)
複製代碼
mix-in
實現相同的結果輸出的話,就不要使用多繼承了。mix-in
類須要的時候,在實例級別上使用可插拔的行爲能夠爲每個自定義的類工做的更好。mix-in
。若是你發現本身渴望隨繼承的便利和封裝,那麼考慮mix-in
吧。它是一個只定義了幾個類必備功能方法的很小的類。Mix-in
類不定義以本身的實例屬性,也不須要它們的初始化方法__init__
被調用。Mix-in
能夠被分層和組織成最小化的代碼塊,方便代碼的重用。
mix-in 是能夠替換的 class ,一般只定義 methods ,雖然本質上上仍是經過繼承的方式,但由於 mix-in 沒有本身的 state ,也就是說沒有定義 attributes ,使用上更有彈性。
範例1:
注:hasattr 函數動態訪問屬性,isinstance 函數動態檢測對象類型
import json
class ToDictMixin(object):
def to_dict(self):
return self._traverse_dict(self.__dict__)
def _traverse_dict(self, instance_dict):
output = {}
for key, value in instance_dict.items():
output[key] = self._traverse(key, value)
return output
def _traverse(self, key, value):
if isinstance(value, ToDictMixin):
return value.to_dict()
elif isinstance(value, dict):
return self._traverse_dict(value)
elif isinstance(value, list):
return [self._traverse(key, i) for i in value]
elif hasattr(value, '__dict__'):
return self._traverse_dict(value.__dict__)
else:
return value
複製代碼
使用示例:
class BinaryTree(ToDIctMixin):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
# 這下把大量的Python對象轉換到一個字典中變得容易多了。
tree = BinaryTree(10, left=BinaryTree(7, right=BinaryTree(9)),
right=BinaryTree(13, left=BinaryTree(11)))
print(tree.to_dict())
>>>
{'left': {'left': None,
'right': {'left': None, 'right': None, 'value': 9},
'value': 7},
'right': {'left': {'left': None, 'right': None, 'value': 11},
'right': None,
'value': 13},
'value': 10
}
複製代碼
範例2:
# 在這個例子中,惟一的必須條件就是類中必須有一個to_dict方法和接收關鍵字參數的__init__構造方法
class JsonMixin(object):
@classmethod
def from_json(cls, data):
kwargs = json.loads(data)
return cls(**kwargs)
def to_json(self):
return json.dumps(self.to_dict())
class DatacenterRack(ToDictMixin, JsonMixin):
def __init__(self, switch=None, machines=None):
self.switch = Switch(**switch)
self.machines = [Machine(**kwargs) for kwargs in machines]
class Switch(ToDictMixin, JsonMixin):
def __init__(self, ports=None, speed=None):
self.ports = ports
self.speed = speed
class Machine(ToDictMixin, JsonMixin):
def __init__(self, cores=None, ram=None, disk=None):
self.cores = cores
self.ram = ram
self.disk = disk
# 將這些類從JSON傳中序列化也是簡單的。這裏我校驗了一下,保證數據能夠在序列化和反序列化正常的轉換。
serialized = """{ "switch": {"ports": 5, "speed": 1e9}, "machines": [ {"cores": 8, "ram": 32e9, "disk": 5e12}, {"cores": 4, "ram": 16e9, "disk": 1e12}, {"cores": 2, "ram": 4e9, "disk": 500e9} ] }"""
deserialized = DatacenterRack.from_json(serialized)
roundtrip = deserialized.to_json()
assert json.loads(serialized) == json.loads(roundtrip)
複製代碼
Python 裏面沒有真正的 "private variable",想存取均可以存取獲得。
下面的程式看起來咱們沒辦法獲得 __private_field
class MyObject(object):
def __init__(self):
self.public_field = 5
self.__private_field = 10
def get_private_field(self):
return self.__private_field
foo = MyObject()
print(foo.__private_field) # AttributeError
複製代碼
但其實只是名稱被改掉而已
print(foo.__dict__)
# {'_MyObject__private_field': 10, 'public_field': 5}
print(foo._MyObject__private_field)
複製代碼
通常來講 Python 慣例是在變數前加一個底線表明 **protected variable **,做用在於提醒開發者使用上要注意。
class MyClass(object):
def __init__(self, value):
# This stores the user-supplied value for the object.
# It should be coercible to a string. Once assigned for
# the object it should be treated as immutable.
self._value = value
def get_value(self):
return str(self._value)
class MyIntegerSubclass(MyClass):
def get_value(self):
return self._value
foo = MyIntegerSubclass(5)
assert foo.get_value() == 5
複製代碼
雙底線的命名方式是爲了不父類和子類間的命名衝突,除此以外儘可能避免使用這種命名。
collections.abc
裏面的 abstract classes 的做用是讓開發者方便地開發本身的 container ,例如 list。通常情況下繼承list 就ok了,可是當結構比較複雜的時候就須要本身自定義,例如 list 有許多 方法,要一一實現有點麻煩。
下面程式中 SequenceNode 是想要擁有 list 功能的 binary tree。
class BinaryNode(object):
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
class IndexableNode(BinaryNode):
def _search(self, count, index):
found = None
if self.left:
found, count = self.left._search(count, index)
if not found and count == index:
found = self
else:
count += 1
if not found and self.right:
found, count = self.right._search(count, index)
return found, count
def __getitem__(self, index):
found, _ = self._search(0, index)
if not found:
raise IndexError('Index out of range')
return found.value
class SequenceNode(IndexableNode):
def __len__(self):
_, count = self._search(0, None)
return count
複製代碼
如下是 SequenceNode的一些 list 經常使用的操做
tree = SequenceNode(
10,
left=SequenceNode(
5,
left=SequenceNode(2),
right=SequenceNode(
6,
right=SequenceNode(7))),
right=SequenceNode(
15,
left=SequenceNode(11)))
print('Index 0 =', tree[0])
print('11 in the tree?', 11 in tree)
print('Tree has %d nodes' % len(tree))
>>>
Index 0 = 2
11 in the tree? True
Tree has 7 nodes
複製代碼
可是使用者可能想使用像 count()
以及 index()
等 list 的 方法 ,這時候可使用 collections.abc
的 **Sequence **。子類只要實現 __getitem__
以及 __len__
, **Sequence **以及提供count()
以及 index()
了,並且若是子類沒有實現相似 Sequence 的抽象基類所要求的每一個方法,collections.abc
模塊就會指出這個錯誤。
from collections.abc import Sequence
class BetterNode(SequenceNode, Sequence):
pass
tree = BetterNode(
# ...
)
print('Index of 7 is', tree.index(7))
print('Count of 10 is', tree.count(10))
>>>
Index of 7 is 3
Count of 10 is 1
複製代碼
示例1:
不要把 java 的那一套 getter 和 setter 帶進來
not:
class OldResistor(object):
def __init__(self, ohms):
self._ohms = ohms
def get_ohms(self):
return self._ohms
def set_ohms(self, ohms):
self._ohms = ohms
複製代碼
prefer:
class Resistor(object):
def __init__(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0
複製代碼
示例2:
使用@property,來綁定一些特殊操做,可是不要產生奇怪的反作用,好比在getter裏面作一些賦值的操做
class VoltageResistance(Resistor):
def __init__(self, ohms):
super().__init__(ohms)
self._voltage = 0
# 至關於 getter
@property
def voltage(self):
return self._voltage
# 至關於 setter
@voltage.setter
def voltage(self, voltage):
self._voltage = voltage
self.current = self._voltage / self.ohms
r2 = VoltageResistance(1e3)
print('Before: %5r amps' % r2.current)
# 會執行 setter 方法
r2.voltage = 10
print('After: %5r amps' % r2.current)
複製代碼
@property能夠把簡單的數值屬性遷移爲實時計算,只定義 getter 不定義 setter 那麼就是一個只讀屬性
class Bucket(object):
def __init__(self, period):
self.period_delta = timedelta(seconds=period)
self.reset_time = datetime.now()
self.max_quota = 0
self.quota_consumed = 0
def __repr__(self):
return ('Bucket(max_quota=%d, quota_consumed=%d)' %
(self.max_quota, self.quota_consumed))
@property
def quota(self):
return self.max_quota - self.quota_consumed
@quota.setter
def quota(self, amount):
delta = self.max_quota - amount
if amount == 0:
# Quota being reset for a new period
self.quota_consumed = 0
self.max_quota = 0
elif delta < 0:
# Quota being filled for the new period
assert self.quota_consumed = 0
self.max_quota = amount
else:
# Quota being consumed during the period
assert self.max_quota >= self,quota_consumed
self.quota_consumed += delta
複製代碼
這種寫法的好處就在於:從前使用的Bucket.quota 的那些舊代碼,既不須要作出修改,也不須要擔憂如今的Bucket類是如何實現的,能夠輕鬆無痛擴展新功能。可是@property也不能濫用,並且@property的一個缺點就是沒法被複用,同一套邏輯不能在不一樣的屬性之間重複使用若是不停的編寫@property方法,那就意味着當前這個類的代碼寫的確實很糟糕,此時應該重構了。
TODO
若是想複用 @property 方法及其驗證機制,那麼能夠自定義描述符類
WeakKeyDictionary 能夠保證描述符類不會泄露內存
經過描述符協議來實現屬性的獲取和設置操做時,不要糾結於__getatttttribute__
的方法的具體運做細節
property
最大的問題是可能形成 duplicated code 這種 code smell。
下面的程式 math_grade
以及 math_grade
就有這樣的問題。
class Exam(object):
def __init__(self):
self._writing_grade = 0
self._math_grade = 0
@staticmethod
def _check_grade(value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
@property
def writing_grade(self):
return self._writing_grade
@writing_grade.setter
def writing_grade(self, value):
self._check_grade(value)
self._writing_grade = value
@property
def math_grade(self):
return self._math_grade
@math_grade.setter
def math_grade(self, value):
self._check_grade(value)
self._math_grade = value
複製代碼
可使用 **descriptor **解決,下面的程式將重複的邏輯封裝在 Grade 裏面。可是這個程式根本 **不能用 **,由於存取到的是 class attributes,例如 exam.writing_grade = 40
實際上是Exam.__dict__['writing_grade'].__set__(exam, 40)
,這樣全部 Exam 的 instances 都是存取到同樣的東西 ( Grade()
)。
class Grade(object):
def __init__(self):
self._value = 0
def __get__(self, instance, instance_type):
return self._value
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self._value = value
class Exam(object):
math_grade = Grade()
writing_grade = Grade()
science_grade = Grade()
exam = Exam()
exam.writing_grade = 40
複製代碼
解決方式是用個 dictionary 存起來,這裏使用 WeakKeyDictionary
避免 memory leak。
from weakref import WeakKeyDictionary
class Grade(object):
def __init__(self):
self._values = WeakKeyDictionary()
def __get__(self, instance, instance_type):
if instance is None: return self
return self._values.get(instance, 0)
def __set__(self, instance, value):
if not (0 <= value <= 100):
raise ValueError('Grade must be between 0 and 100')
self._values[instance] = value
複製代碼
__getattr__
, __getattribute__
, 和__setattr__
實現按需生產的屬性__getttattr__
和 __setattr__
,咱們能夠用惰性的方式來加載並保存對象的屬性__getattr__
和 __getattribute__
的區別:前者只會在待訪問的屬性缺失時觸發,然後者則會在每次訪問屬性的時候觸發__getattributte__
和 __setattr__
方法中訪問實例屬性,那麼應該直接經過 super() 來作,以免無限遞歸__getattr__
和 __getattribute__
均可以動態地存取 attributes ,不一樣點在於若是 __dict__
找不到纔會呼叫 __getattr__
,而 __getattribute__
每次都會被呼叫到。
class LazyDB(object):
def __init__(self):
self.exists = 5
def __getattr__(self, name):
value = 'Value for %s' % name
setattr(self, name, value)
return value
class LoggingLazyDB(LazyDB):
def __getattr__(self, name):
print('Called __getattr__(%s)' % name)
return super().__getattr__(name)
data = LoggingLazyDB()
print('exists:', data.exists)
print('foo: ', data.foo)
print('foo: ', data.foo)
複製代碼
class ValidatingDB(object):
def __init__(self):
self.exists = 5
def __getattribute__(self, name):
print('Called __getattribute__(%s)' % name)
try:
return super().__getattribute__(name)
except AttributeError:
value = 'Value for %s' % name
setattr(self, name, value)
return value
data = ValidatingDB()
print('exists:', data.exists)
print('foo: ', data.foo)
print('foo: ', data.foo)
複製代碼
能夠控制什麼 attributes 不該該被使用到,記得要丟 **AttributeError **。
try:
class MissingPropertyDB(object):
def __getattr__(self, name):
if name == 'bad_name':
raise AttributeError('%s is missing' % name)
value = 'Value for %s' % name
setattr(self, name, value)
return value
data = MissingPropertyDB()
data.foo # Test this works
data.bad_name
except:
logging.exception('Expected')
else:
assert False
複製代碼
__setattr__
每次都會被呼叫到。
class SavingDB(object):
def __setattr__(self, name, value):
# Save some data to the DB log
pass
super().__setattr__(name, value)
class LoggingSavingDB(SavingDB):
def __setattr__(self, name, value):
print('Called __setattr__(%s, %r)' % (name, value))
super().__setattr__(name, value)
複製代碼
很重要的一點是 __setattr__
以及 __getattribute__
必定要呼叫父類的 __getattribute__
,避免無限循環下去。
這個會爆掉,由於存取 self._data
又會呼叫 __getattribute__
。
class BrokenDictionaryDB(object):
def __init__(self, data):
self._data = {}
def __getattribute__(self, name):
print('Called __getattribute__(%s)' % name)
return self._data[name]
複製代碼
呼叫 super().__getattribute__('_data')
class DictionaryDB(object):
def __init__(self, data):
self._data = data
def __getattribute__(self, name):
data_dict = super().__getattribute__('_data')
return data_dict[name]
複製代碼
__new__
方法首先,定義元類,咱們要繼承 type, python 默認會把那些類的 class 語句體中所含的相關內容,發送給元類的 new 方法。
class Meta(type):
def __new__(meta, name, bases, class_dict):
print(meta, name, bases, class_dict)
return type.__new__(meta, name, bases, class_dict)
# 這是 python2 寫法
class MyClassInPython2(object):
__metaclass__ = Meta
stuff = 123
def foo(self):
pass
# python 3
class MyClassInPython3(object, metaclass=Meta):
stuff = 123
def foo(self):
pass
class ValidatePolygon(type):
def __new__(meta, name, bases, class_dict):
# Don't validate the abstract Polygon class
if bases != (object,):
if class_dict['sides'] < 3:
raise ValueError('Polygons need 3+ sides')
return type.__new__(meta, name, bases, class_dict)
class Polygon(object, metaclass=ValidatePolygon):
sides = None # Specified by subclasses
@classmethod
def interior_angles(cls):
return (cls.sides - 2) * 180
class Triangle(Polygon):
sides = 3
print(Triangle.interior_angles())
複製代碼
最基本的
import subprocess
proc = subprocess.Popen(
['echo', 'Hello from the child!'],
stdout=subprocess.PIPE)
out, err = proc.communicate()
print(out.decode('utf-8'))
複製代碼
傳入資料
import os
def run_openssl(data):
env = os.environ.copy()
env['password'] = b'\xe24U\n\xd0Ql3S\x11'
proc = subprocess.Popen(
['openssl', 'enc', '-des3', '-pass', 'env:password'],
env=env,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
proc.stdin.write(data)
proc.stdin.flush() # Ensure the child gets input
return proc
def run_md5(input_stdin):
proc = subprocess.Popen(
['md5'],
stdin=input_stdin,
stdout=subprocess.PIPE)
return proc
複製代碼
模擬 pipes
input_procs = []
hash_procs = []
for _ in range(3):
data = os.urandom(10)
proc = run_openssl(data)
input_procs.append(proc)
hash_proc = run_md5(proc.stdout)
hash_procs.append(hash_proc)
for proc in input_procs:
proc.communicate()
for proc in hash_procs:
out, err = proc.communicate()
print(out.strip())
複製代碼
比較有趣的是 **Barrier **這個 Python 3.2 才加進來的東西,之前要用 **Semaphore **來作。
from threading import Barrier
from threading import Thread
from threading import Lock
class LockingCounter(object):
def __init__(self):
self.lock = Lock()
self.count = 0
def increment(self, offset):
with self.lock:
self.count += offset
class LockingCounter(object):
def __init__(self):
self.lock = Lock()
self.count = 0
def increment(self, offset):
with self.lock:
self.count += offset
def worker(sensor_index, how_many, counter):
# I have a barrier in here so the workers synchronize
# when they start counting, otherwise it's hard to get a race
# because the overhead of starting a thread is high.
BARRIER.wait()
for _ in range(how_many):
# Read from the sensor
counter.increment(1)
def run_threads(func, how_many, counter):
threads = []
for i in range(5):
args = (i, how_many, counter)
thread = Thread(target=func, args=args)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
BARRIER = Barrier(5)
counter = LockingCounter()
run_threads(worker, how_many, counter)
print('Counter should be %d, found %d' %
(5 * how_many, counter.count))
複製代碼
from queue import Queue
from threading import Thread
class ClosableQueue(Queue):
SENTINEL = object()
def close(self):
self.put(self.SENTINEL)
def __iter__(self):
while True:
item = self.get()
try:
if item is self.SENTINEL:
return # Cause the thread to exit
yield item
finally:
self.task_done()
class StoppableWorker(Thread):
def __init__(self, func, in_queue, out_queue):
super().__init__()
self.func = func
self.in_queue = in_queue
self.out_queue = out_queue
def run(self):
for item in self.in_queue:
result = self.func(item)
self.out_queue.put(result)
def download(item):
return item
def resize(item):
return item
def upload(item):
return item
download_queue = ClosableQueue()
resize_queue = ClosableQueue()
upload_queue = ClosableQueue()
done_queue = ClosableQueue()
threads = [
StoppableWorker(download, download_queue, resize_queue),
StoppableWorker(resize, resize_queue, upload_queue),
StoppableWorker(upload, upload_queue, done_queue),
]
for thread in threads:
thread.start()
for _ in range(1000):
download_queue.put(object())
download_queue.close()
download_queue.join()
resize_queue.close()
resize_queue.join()
upload_queue.close()
upload_queue.join()
print(done_queue.qsize(), 'items finished')
複製代碼
線程有三個大問題:
coroutine只有1kb的內存消耗
generator能夠經過send方法把值傳遞給yield
def my_coroutine():
while True:
received = yield
print("Received:", received)
it = my_coroutine()
next(it)
it.send("First")
('Received:', 'First')
複製代碼
Python2不支持直接yield generator,可使用for循環yield
使用 concurrent.futures
裏面的 **ProcessPoolExecutor **能夠很簡單地平行處理 CPU-bound 的程式,免得用 multiprocessing
自定義。
from concurrent.futures import ProcessPoolExecutor
start = time()
pool = ProcessPoolExecutor(max_workers=2) # The one change
results = list(pool.map(gcd, numbers))
end = time()
print('Took %.3f seconds' % (end - start))
複製代碼
裝飾器能夠對函數進行封裝,可是會改變函數信息
使用 functools 的 warps 能夠解決這個問題
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
# …
return wrapper
@trace
def fibonacci(n):
# …
複製代碼
contextlib.contextmanager
,方便咱們在作 **context managers **。
from contextlib import contextmanager
@contextmanager
def log_level(level, name):
logger = logging.getLogger(name)
old_level = logger.getEffectiveLevel()
logger.setLevel(level)
try:
yield logger
finally:
logger.setLevel(old_level)
with log_level(logging.DEBUG, 'my-log') as logger:
logger.debug('This is my message!')
logging.debug('This will not print')
logger = logging.getLogger('my-log')
logger.debug('Debug will not print')
logger.error('Error will print')
複製代碼
使用 copyreg
這個內建的 module ,搭配 pickle
使用。
pickle
使用上很簡單,假設咱們有個 class:
class GameState(object):
def __init__(self):
self.level = 0
self.lives = 4
state = GameState()
state.level += 1 # Player beat a level
state.lives -= 1 # Player had to try again
複製代碼
能夠用 pickle
保存 object
import pickle
state_path = '/tmp/game_state.bin'
with open(state_path, 'wb') as f:
pickle.dump(state, f)
with open(state_path, 'rb') as f:
state_after = pickle.load(f)
# {'lives': 3, 'level': 1}
print(state_after.__dict__)
複製代碼
可是若是增長了新的 field, game_state.bin
load 回來的 object 固然不會有新的 field (points),但是它仍然是 GameState 的 instance,這會形成混亂。
class GameState(object):
def __init__(self):
self.level = 0
self.lives = 4
self.points = 0
with open(state_path, 'rb') as :
state_after = pickle.load(f)
# {'lives': 3, 'level': 1}
print(state_after.__dict__)
assert isinstance(state_after, GameState)
複製代碼
使用 copyreg
能夠解決這個問題,它能夠註冊用來 serialize Python 物件的函式。
pickle_game_state()
返回一個 tuple ,包含了拿來 unpickle 的函式以及傳入函式的引數。
import copyreg
class GameState(object):
def __init__(self, level=0, lives=4, points=0):
self.level = level
self.lives = lives
self.points = points
def pickle_game_state(game_state):
kwargs = game_state.__dict__
return unpickle_game_state, (kwargs,)
def unpickle_game_state(kwargs):
return GameState(**kwargs)
copyreg.pickle(GameState, pickle_game_state)
複製代碼
copyreg
也能夠拿來記錄版本,達到向後相容的目的。
若是原先的 class 以下
class GameState(object):
def __init__(self, level=0, lives=4, points=0, magic=5):
self.level = level
self.lives = lives
self.points = points
self.magic = magic
state = GameState()
state.points += 1000
serialized = pickle.dumps(state)
複製代碼
後來修改了,拿掉 lives ,這時原先使用預設參數的作法不能用了。
class GameState(object):
def __init__(self, level=0, points=0, magic=5):
self.level = level
self.points = points
self.magic = magic
# TypeError: __init__() got an unexpected keyword argument 'lives'
pickle.loads(serialized)
複製代碼
在 serialize 時多加上版本號, deserialize 時加以判斷
def pickle_game_state(game_state):
kwargs = game_state.__dict__
kwargs['version'] = 2
return unpickle_game_state, (kwargs,)
def unpickle_game_state(kwargs):
version = kwargs.pop('version', 1)
if version == 1:
kwargs.pop('lives')
return GameState(**kwargs)
copyreg.pickle(GameState, pickle_game_state)
複製代碼
重寫程式時,若是 class 更名了,想要 load 的 serialized 物件固然不能用,但仍是可使用 copyreg
解決。
class BetterGameState(object):
def __init__(self, level=0, points=0, magic=5):
self.level = level
self.points = points
self.magic = magic
copyreg.pickle(BetterGameState, pickle_game_state)
複製代碼
能夠發現 unpickle_game_state()
的 path 進入 dump 出來的資料中,固然這樣作的缺點就是 unpickle_game_state()
所在的 module 不能改 path 了。
state = BetterGameState()
serialized = pickle.dumps(state)
print(serialized[:35])
>>>
b'\x80\x03c__main__\nunpickle_game_state\nq\x00}'
複製代碼
內置算法和數據結構
collections.deque
collections.OrderedDict
collection.defaultdict
heapq模塊操做list(優先隊列):heappush,heappop和nsmallest
a = []
heappush(a, 5)
heappush(a, 3)
heappush(a, 7)
heappush(a, 4)
print(heappop(a), heappop(a), heappop(a), heappop(a))
# >>>
# 3 4 5 7
複製代碼
bisect模塊:bisect_left能夠對有序列表進行高效二分查找
itertools模塊(Python2不必定支持):
Effective Python(英文版) PDF 密碼: 7v9r
Effecttive Python(中文不完整非掃描版) PDF 密碼: 86bm
Effective Python(中文掃描版) PDF 密碼: dg7w