1. 數(shù)據(jù)結(jié)構(gòu)和算法
1.1 分解記錄
record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
name, email, *phone_numbers = user_record
1.2 找到最大或最小的N個元素
import heapq
nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
print(heapq.nlargest(3, nums)) # Prints [42, 37, 23]
print(heapq.nsmallest(3, nums)) # Prints [-4, 1, 2]
這里可以接受一個參數(shù)key吃溅。
portfolio = [
{'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'AAPL', 'shares': 50, 'price': 543.22},
{'name': 'FB', 'shares': 200, 'price': 21.09},
{'name': 'HPQ', 'shares': 35, 'price': 31.75},
{'name': 'YHOO', 'shares': 45, 'price': 16.35},
{'name': 'ACME', 'shares': 75, 'price': 115.65}
]
cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price'])
expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price'])
1.3 實現(xiàn)優(yōu)先級隊列
這里的重點是heapq模塊的使用疫诽。
import heapq
class PriorityQueue:
def __init__(self):
self._queue = []
self._index = 0
def push(self, item, priority):
heapq.heappush(self._queue, (-priority, self._index, item))
self._index += 1
def pop(self):
return heapq.heappop(self._queue)[-1]
使用這個類
>>> class Item:
... def __init__(self, name):
... self.name = name
... def __repr__(self):
... return 'Item({!r})'.format(self.name)
...
>>> q = PriorityQueue()
>>> q.push(Item('foo'), 1)
>>> q.push(Item('bar'), 5)
>>> q.push(Item('spam'), 4)
>>> q.push(Item('grok'), 1)
>>> q.pop()
Item('bar')
>>> q.pop()
Item('spam')
>>> q.pop()
Item('foo')
>>> q.pop()
Item('grok')
1.4 在字典中將鍵映射到多個值上
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)
d = defaultdict(set)
d['a'].add(1)
d['a'].add(2)
d['b'].add(4)
1.5 與字典有關(guān)的計算問題
prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
min_price = min(zip(prices.values(), prices.keys()))
# min_price is (10.75, 'FB')
max_price = max(zip(prices.values(), prices.keys()))
# max_price is (612.78, 'AAPL')
prices_sorted = sorted(zip(prices.values(), prices.keys()))
# prices_sorted is [(10.75, 'FB'), (37.2, 'HPQ'),
# (45.23, 'ACME'), (205.55, 'IBM'),
# (612.78, 'AAPL')]
1.6 在兩個字典中尋找相同點
a = {
'x' : 1,
'y' : 2,
'z' : 3
}
b = {
'w' : 10,
'x' : 11,
'y' : 2
}
# Find keys in common
a.keys() & b.keys() # { 'x', 'y' }
# Find keys in a that are not in b
a.keys() - b.keys() # { 'z' }
# Find (key,value) pairs in common
a.items() & b.items() # { ('y', 2) }
1.7 從序列中移除重復(fù)項且保持元素間順序不變
def dedupe(items, key=None):
seen = set()
for item in items:
val = item if key is None else key(item)
if val not in seen:
yield item
seen.add(val)
>>> a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]
>>> list(dedupe(a, key=lambda d: (d['x'],d['y'])))
[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
>>> list(dedupe(a, key=lambda d: d['x']))
[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
1.8 找出序列中出現(xiàn)次數(shù)最多的元素
words = [
'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
'my', 'eyes', "you're", 'under'
]
from collections import Counter
word_counts = Counter(words)
top_three = word_counts.most_common(3)
print(top_three)
# Outputs [('eyes', 8), ('the', 5), ('look', 4)]
1.9 通過公共鍵對字典列表排序
rows = [
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
]
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
1.10 排序不支持原生比較的對象
內(nèi)置的sorted() 函數(shù)有一個關(guān)鍵字參數(shù)key 至朗,可以傳入一個callable 對象給它讥蔽,這個callable 對象對每個傳入的對象返回一個值,這個值會被sorted 用來排序這些對象篷角。
class User:
def __init__(self, user_id):
self.user_id = user_id
def __repr__(self):
return 'User({})'.format(self.user_id)
def sort_notcompare():
users = [User(23), User(3), User(99)]
print(users)
print(sorted(users, key=lambda u: u.user_id))
from operator import attrgetter
print(sorted(users, key=attrgetter('user_id')))
# print(sorted(users, key=attrgetter('last_name', 'first_name')))
print(min(users, key=attrgetter('user_id')))
print(max(users, key=attrgetter('user_id')))
sort_notcompare()
#[User(23), User(3), User(99)]
#[User(3), User(23), User(99)]
#[User(3), User(23), User(99)]
#User(3)
#User(99)
1.11 根據(jù)字段將記錄分組
rows = [
{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]
from operator import itemgetter
from itertools import groupby
# Sort by the desired field first
rows.sort(key=itemgetter('date'))
# Iterate in groups
for date, items in groupby(rows, key=itemgetter('date')):
print(date)
for i in items:
print(' ', i)
1.12 將多個映射合并為單個映射
a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }
from collections import ChainMap
c = ChainMap(a,b)
print(c['x']) # Outputs 1 (from a)
print(c['y']) # Outputs 2 (from b)
print(c['z']) # Outputs 3 (from a)
2. 字符串和文本
2.1 針對任意多的分隔符拆分字符串
當你使用re.split() 函數(shù)時候焊刹,需要特別注意的是正則表達式中是否包含一個括號捕獲分組。如果使用了捕獲分組恳蹲,那么被匹配的文本也將出現(xiàn)在結(jié)果列表中虐块。
如果你不想保留分割字符串到結(jié)果列表中去盈滴,但仍然需要使用到括號來分組正則表達式的話苍柏,確保你的分組是非捕獲分組,形如(?:...) 隆嗅。
>>> line = 'asdf fjdk; afed, fjek,asdf, foo'
>>> import re
>>> re.split(r'[;,\s]\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
>>> fields = re.split(r'(;|,|\s)\s*', line)
>>> fields
['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'foo']
>>> re.split(r'(?:,|;|\s)\s*', line)
['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
2.2 文本模式的匹配和查找
>>> datepat = re.compile(r'\d+/\d+/\d+')
>>> if datepat.match(text1):
print('yes')
else:
print('no')
>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> datepat.findall(text)
['11/27/2012', '3/13/2013']
3. 迭代器和生成器
3.1 用生成器創(chuàng)建新的迭代模式
def frange(start, stop, increment):
x = start
while x < stop:
yield x
x += increment
>>> list(frange(0, 1, 0.125))
[0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875]
一個函數(shù)中需要有一個yield 語句即可將其轉(zhuǎn)換為一個生成器错忱。
3.2 實現(xiàn)迭代協(xié)議
我們用Node類來表示樹結(jié)構(gòu)敞嗡。
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
3.3 跳過可迭代對象中的前一部分元素
itertools 模塊中有一些函數(shù)可以完成這個任務(wù)。首先介紹的是itertools.dropwhile() 函數(shù)航背。使用時喉悴,你給它傳遞一個函數(shù)對象和一個可迭代對象。它會返回一個迭代器對象玖媚,丟棄原有序列中直到函數(shù)返回True 之前的所有元素箕肃,然后返回后面所有元素。
from itertools import dropwhile
with open('/etc/passwd') as f:
#跳過開始部分的注釋行
for line in dropwhile(lambda line: line.startswith('#'), f):
print(line, end='')
如果你已經(jīng)明確知道了要跳過的元素的個數(shù)的話今魔,那么可以使用itertools.islice() 來代替勺像。
from itertools import islice
items = ['a', 'b', 'c', 1, 4, 10, 15]
for x in islice(items, 3, None):
print(x)
3.4 迭代所有可能的組合和排列
>>> items = ['a', 'b', 'c']
>>> from itertools import permutations
>>> for p in permutations(items):
... print(p)
...
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')
3.5 以索引-值對的形式迭代序列
>>> my_list = ['a', 'b', 'c']
>>> for idx, val in enumerate(my_list):
... print(idx, val)
...
0 a
1 b
2 c
3.6 在不同的容器中進行迭代
itertools.chain() 方法可以用來簡化這個任務(wù)障贸。它接受一個可迭代對象列表作為輸入,并返回一個迭代器
>>> from itertools import chain
>>> a = [1, 2, 3, 4]
>>> b = ['x', 'y', 'z']
>>> for x in chain(a, b):
... print(x)
...
1
2
3
4
x
y
z
3.7 創(chuàng)建處理數(shù)據(jù)的管道
假如我們有海量的數(shù)據(jù)需要處理吟宦,但是沒法完全將數(shù)據(jù)加載到內(nèi)存中篮洁。生成器函數(shù)是一個實現(xiàn)管道機制的好辦法。
import os
import fnmatch
import gzip
import bz2
import re
def gen_find(filepat, top):
'''
Find all filenames in a directory tree that match a shell wildcard pattern
'''
for path, dirlist, filelist in os.walk(top):
for name in fnmatch.filter(filelist, filepat):
yield os.path.join(path,name)
def gen_opener(filenames):
'''
Open a sequence of filenames one at a time producing a file object.
The file is closed immediately when proceeding to the next iteration.
'''
for filename in filenames:
if filename.endswith('.gz'):
f = gzip.open(filename, 'rt')
elif filename.endswith('.bz2'):
f = bz2.open(filename, 'rt')
else:
f = open(filename, 'rt')
yield f
f.close()
def gen_concatenate(iterators):
'''
Chain a sequence of iterators together into a single sequence.
'''
for it in iterators:
yield from it
def gen_grep(pattern, lines):
'''
Look for a regex pattern in a sequence of lines
'''
pat = re.compile(pattern)
for line in lines:
if pat.search(line):
yield line
找出所有包含關(guān)鍵字python的日志行殃姓。
lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)
for line in pylines:
print(line)
找出傳送的字節(jié)數(shù)并統(tǒng)計出總字節(jié)量袁波。
lognames = gen_find('access-log*', 'www')
files = gen_opener(lognames)
lines = gen_concatenate(files)
pylines = gen_grep('(?i)python', lines)
bytecolumn = (line.rsplit(None,1)[1] for line in pylines)
bytes = (int(x) for x in bytecolumn if x != '-')
print('Total', sum(bytes))
3.8 扁平化處理嵌套型的序列
一個包含yield from 語句的遞歸生成器來輕松解決這個問題。
from collections import Iterable
def flatten(items, ignore_types=(str, bytes)):
for x in items:
if isinstance(x, Iterable) and not isinstance(x, ignore_types):
yield from flatten(x)
else:
yield x
items = [1, 2, [3, 4, [5, 6], 7], 8]
# Produces 1 2 3 4 5 6 7 8
for x in flatten(items):
print(x)
3.9 合并多個有序序列蜗侈,再對整個有序序列進行迭代
>>> import heapq
>>> a = [1, 4, 7, 10]
>>> b = [2, 5, 6, 11]
>>> for c in heapq.merge(a, b):
... print(c)
...
1
2
4
5
6
7
10
11
4. 函數(shù)
4.1 在匿名函數(shù)中綁定變量的值
你用lambda 定義了一個匿名函數(shù)篷牌,并想在定義時捕獲到某些變量的值。
x = 10
a = lambda y: x + y
x = 20
b = lambda y: x + y
print(a(10))
#30
print(b(10))
#30
這其中的奧妙在于lambda 表達式中的x 是一個自由變量踏幻,在運行時綁定值枷颊,而不是定義時就綁定,這跟函數(shù)的默認值參數(shù)定義是不同的该面。因此夭苗,在調(diào)用這個lambda 表達式的時候,x 的值是執(zhí)行時的值隔缀。
如果你想讓某個匿名函數(shù)在定義時就捕獲到值听诸,可以將那個參數(shù)值定義成默認參數(shù)即可。
x = 10
a = lambda y, x=x: x + y
x = 20
b = lambda y, x=x: x + y
print(a(10))
#20
print(b(10))
#30
funcs = [lambda x: x+n for n in range(5)]
print([f(0) for f in funcs])
#[4, 4, 4, 4, 4]
funcs = [lambda x, n=n: x+n for n in range(5)]
print([f(0) for f in funcs])
#[0, 1, 2, 3, 4]
4.2 在回調(diào)函數(shù)中攜帶額外的狀態(tài)
def apply_async(func, args, *, callback):
# Compute the result
result = func(*args)
# Invoke the callback with the result
callback(result)
class ResultHandler:
def __init__(self):
self.sequence = 0
def handler(self, result):
self.sequence += 1
print('[{}] Got: {}'.format(self.sequence, result))
>>> r = ResultHandler()
>>> apply_async(add, (2, 3), callback=r.handler)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=r.handler)
[2] Got: helloworld
這里也可以使用閉包
def make_handler():
sequence = 0
def handler(result):
nonlocal sequence
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
return handler
>>> handler = make_handler()
>>> apply_async(add, (2, 3), callback=handler)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=handler)
[2] Got: helloworld
有時候可以利用協(xié)程來完成同樣的任務(wù)
def make_handler():
sequence = 0
while True:
result = yield
sequence += 1
print('[{}] Got: {}'.format(sequence, result))
>>> handler = make_handler()
>>> next(handler) # Advance to the yield
>>> apply_async(add, (2, 3), callback=handler.send)
[1] Got: 5
>>> apply_async(add, ('hello', 'world'), callback=handler.send)
[2] Got: helloworld
5. 類與對象
5.1 修改實例的字符串表示
可以通過定義 __str__()和__repr__()方法來實現(xiàn)蚕泽。
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair({0.x!r}, {0.y!r})'.format(self)
def __str__(self):
return '({0.x!s}, {0.y!s})'.format(self)
5.2 當創(chuàng)建大量實例時如何節(jié)省內(nèi)存
class Date:
__slots__ = ['year', 'month', 'day']
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
5.3 創(chuàng)建可管理的屬性
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function (optional)
@first_name.deleter
def first_name(self):
raise AttributeError("Can't delete attribute")
>>> a = Person('Guido')
>>> a.first_name # Calls the getter
'Guido'
>>> a.first_name = 42 # Calls the setter
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "prop.py", line 14, in first_name
raise TypeError('Expected a string')
TypeError: Expected a string
>>> del a.first_name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
5.4 調(diào)用父類中的方法
class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
5.5 創(chuàng)建一種新形式的類屬性或?qū)嵗龑傩?/h3>
# Descriptor attribute for an integer type-checked attribute
class Integer:
def __init__(self, name):
self.name = name
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
class Point:
x = Integer('x')
y = Integer('y')
def __init__(self, x, y):
self.x = x
self.y = y
>>> p = Point(2, 3)
>>> p.x # Calls Point.x.__get__(p,Point)
2
>>> p.y = 5 # Calls Point.y.__set__(p, 5)
>>> p.x = 2.3 # Calls Point.x.__set__(p, 2.3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "descrip.py", line 12, in __set__
raise TypeError('Expected an int')
TypeError: Expected an int
5.6 讓屬性具有惰性求值的能力
# Descriptor attribute for an integer type-checked attribute
class Integer:
def __init__(self, name):
self.name = name
def __get__(self, instance, cls):
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError('Expected an int')
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
class Point:
x = Integer('x')
y = Integer('y')
def __init__(self, x, y):
self.x = x
self.y = y
>>> p = Point(2, 3)
>>> p.x # Calls Point.x.__get__(p,Point)
2
>>> p.y = 5 # Calls Point.y.__set__(p, 5)
>>> p.x = 2.3 # Calls Point.x.__set__(p, 2.3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "descrip.py", line 12, in __set__
raise TypeError('Expected an int')
TypeError: Expected an int
我們想將一個只讀的屬性定義為property屬性方法,只有在訪問它時才參與計算桥嗤。但是须妻,一旦訪問了該屬性,我們希望把計算出的值緩存起來泛领,不要每次訪問它時都重新計算荒吏。
class lazyproperty:
def __init__(self, func):
self.func = func
def __get__(self, instance, cls):
if instance is None:
return self
else:
value = self.func(instance)
setattr(instance, self.func.__name__, value)
return value
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@lazyproperty
def area(self):
print('Computing area')
return math.pi * self.radius ** 2
@lazyproperty
def perimeter(self):
print('Computing perimeter')
return 2 * math.pi * self.radius
>>> c = Circle(4.0)
>>> c.radius
4.0
>>> c.area
Computing area
50.26548245743669
>>> c.area
50.26548245743669
>>> c.perimeter
Computing perimeter
25.132741228718345
>>> c.perimeter
25.132741228718345
5.7 簡化數(shù)據(jù)結(jié)構(gòu)的初始化過程
我們可以將初始化數(shù)據(jù)結(jié)構(gòu)的步驟歸納到一個單獨的__init__()函數(shù)中,并將其定義在一個公共的基類中渊鞋。
class Structure:
_fields= []
def __init__(self, *args, **kwargs):
if len(args) > len(self._fields):
raise TypeError('Expected {} arguments'.format(len(self._fields)))
# Set all of the positional arguments
for name, value in zip(self._fields, args):
setattr(self, name, value)
# Set the remaining keyword arguments
for name in self._fields[len(args):]:
setattr(self, name, kwargs.pop(name))
# Check for any remaining unknown arguments
if kwargs:
raise TypeError('Invalid argument(s): {}'.format(','.join(kwargs)))
# Example use
if __name__ == '__main__':
class Stock(Structure):
_fields = ['name', 'shares', 'price']
s1 = Stock('ACME', 50, 91.1)
s2 = Stock('ACME', 50, price=91.1)
s3 = Stock('ACME', shares=50, price=91.1)
5.8 定義一個接口或抽象基類
要定義一個抽象基類绰更,可以使用abc模塊。
from abc import ABCMeta, abstractmethod
class IStream(metaclass=ABCMeta):
@abstractmethod
def read(self, maxbytes=-1):
pass
@abstractmethod
def write(self, data):
pass
抽象基類的主要用途是強制規(guī)定所需的編程接口锡宋。例如儡湾,一種看待IStream基類的方式就是在高層次上指定一個接口規(guī)范,使其允許讀取和寫入數(shù)據(jù)执俩。
def serialize(obj, stream):
if not isinstance(stream, IStream):
raise TypeError('Expected an IStream')
...
5.9 實現(xiàn)自定義的容器
collections庫中定義了各種各樣的抽象基類徐钠,當實現(xiàn)自定義的容器類它們會非常有用。
import collections
class A(collections.Iterable):
pass
>>> a = A()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class A with abstract methods __iter__
5.10 在類中定義多個構(gòu)造函數(shù)
import time
class Date:
# Primary constructor
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# Alternate constructor
@classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday)
a = Date(2012, 12, 21) # Primary
b = Date.today() # Alternate
5.11 實現(xiàn)帶有狀態(tài)的對象或狀態(tài)機
我們想實現(xiàn)一個狀態(tài)機役首,或者讓對象可以在不同的狀態(tài)中進行操作尝丐。但是我們并不希望代碼里因此出現(xiàn)大量的條件判斷显拜。
一個相對優(yōu)雅的方式是將每種操作狀態(tài)以一個單獨的類來定義,然后在Connection類中使用這些狀態(tài)類爹袁。
class Connection:
def __init__(self):
self.new_state(ClosedConnection)
def new_state(self, state):
self.__class__ = state
def read(self):
raise NotImplementedError()
def write(self, data):
raise NotImplementedError()
def open(self):
raise NotImplementedError()
def close(self):
raise NotImplementedError()
class ClosedConnection(Connection):
def read(self):
raise RuntimeError('Not open')
def write(self, data):
raise RuntimeError('Not open')
def open(self):
self.new_state(OpenConnection)
def close(self):
raise RuntimeError('Already closed')
class OpenConnection(Connection):
def read(self):
print('reading')
def write(self, data):
print('writing')
def open(self):
raise RuntimeError('Already open')
def close(self):
self.new_state(ClosedConnection)
# Example
if __name__ == '__main__':
c = Connection()
print(c)
try:
c.read()
except RuntimeError as e:
print(e)
c.open()
print(c)
c.read()
c.close()
print(c)
5.12 調(diào)用對象上的方法远荠,方法名以字符串形式給出
對于簡單的情況,可能會使用getattr()失息。
import math
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Point({!r:},{!r:})'.format(self.x, self.y)
def distance(self, x, y):
return math.hypot(self.x - x, self.y - y)
p = Point(2,3)
# Method 1 : Use getattr
d = getattr(p, 'distance')(0, 0) # Calls p.distance(0, 0)
另一種方法是使用operator.mathodcaller()譬淳。
import operator
d = operator.methodcaller('distance', 0, 0)(p)
>>> p = Point(3, 4)
>>> d = operator.methodcaller('distance', 0, 0)
>>> d(p)
5.0
5.13 實現(xiàn)訪問者模式
我們需要編寫代碼來處理或遍歷一個由許多不同類型的對象組成的復(fù)雜數(shù)據(jù)結(jié)構(gòu),每種類型的對象處理方式都不相同根时。
# Example of the visitor pattern
# --- The following classes represent nodes in an expression tree
class Node:
pass
class UnaryOperator(Node):
def __init__(self, operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self, left, right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self, value):
self.value = value
# --- The visitor base class
class NodeVisitor:
def visit(self, node):
methname = 'visit_' + type(node).__name__
meth = getattr(self, methname, None)
if meth is None:
meth = self.generic_visit
return meth(node)
def generic_visit(self, node):
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))
# --- Example 1: An expression evaluator
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node.value
def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self, node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self, node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self, node):
return self.visit(node.left) / self.visit(node.right)
def visit_Negate(self, node):
return -node.operand
# --- Example 2: Generate stack instructions
class StackCode(NodeVisitor):
def generate_code(self, node):
self.instructions = []
self.visit(node)
return self.instructions
def visit_Number(self, node):
self.instructions.append(('PUSH', node.value))
def binop(self, node, instruction):
self.visit(node.left)
self.visit(node.right)
self.instructions.append((instruction,))
def visit_Add(self, node):
self.binop(node, 'ADD')
def visit_Sub(self, node):
self.binop(node, 'SUB')
def visit_Mul(self, node):
self.binop(node, 'MUL')
def visit_Div(self, node):
self.binop(node, 'DIV')
def unaryop(self, node, instruction):
self.visit(node.operand)
self.instructions.append((instruction,))
def visit_Negate(self, node):
self.unaryop(node, 'NEG')
# --- Example of the above classes in action
# Representation of 1 + 2 * (3 - 4) / 5
t1 = Sub(Number(3), Number(4))
t2 = Mul(Number(2), t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1), t3)
e = Evaluator()
print('Should get 0.6 :', e.visit(t4))
s = StackCode()
code = s.generate_code(t4)
for c in code:
print(c)
5.14 讓類支持比較操作
Python中的類可以支持比較操作瘦赫,但是如果要實現(xiàn)每種可能的比較操作,那么實現(xiàn)這么多特殊方法則很快會變得繁瑣蛤迎。functools.total_ordering裝飾器可以簡化這個過程确虱。要使用它,可以用它來裝飾一個類替裆,然后定義__eq__()以及另一個比較方法校辩。那么裝飾器就會自動為我們實現(xiàn)其他的比較方法。
from functools import total_ordering
class Room:
def __init__(self, name, length, width):
self.name = name
self.length = length
self.width = width
self.square_feet = self.length * self.width
@total_ordering
class House:
def __init__(self, name, style):
self.name = name
self.style = style
self.rooms = list()
@property
def living_space_footage(self):
return sum(r.square_feet for r in self.rooms)
def add_room(self, room):
self.rooms.append(room)
def __str__(self):
return '{}: {} square foot {}'.format(self.name,
self.living_space_footage,
self.style)
def __eq__(self, other):
return self.living_space_footage == other.living_space_footage
def __lt__(self, other):
return self.living_space_footage < other.living_space_footage
# Build a few houses, and add rooms to them.
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))
h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))
houses = [h1, h2, h3]
print("Is h1 bigger than h2?", h1 > h2) # prints True
print("Is h2 smaller than h3?", h2 < h3) # prints True
print("Is h2 greater than or equal to h1?", h2 >= h1) # prints False
print("Which one is biggest?", max(houses)) # prints 'h3: 1101 square foot Split'
print("Which is smallest?", min(houses)) # prints 'h2: 846 square foot Ranch'
5.15 創(chuàng)建緩存實例
當創(chuàng)建類實例時我們想返回一個緩存引用辆童,讓其指向上一個用同樣參數(shù)創(chuàng)建出的類實例宜咒。
# Simple example
class Spam:
def __init__(self, name):
self.name = name
# Caching support
import weakref
_spam_cache = weakref.WeakValueDictionary()
def get_spam(name):
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s
if __name__ == '__main__':
a = get_spam('foo')
b = get_spam('bar')
print('a is b:', a is b)
c = get_spam('foo')
print('a is c:', a is c)
6. 元編程
6.1 編寫裝飾器時如何保存函數(shù)的元數(shù)據(jù)
import time
from functools import wraps
def timethis(func):
'''
Decorator that reports the execution time.
'''
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end-start)
return result
return wrapper
if __name__ == '__main__':
@timethis
def countdown(n:int):
'''
Counts down
'''
while n > 0:
n -= 1
countdown(100000)
print('Name:', countdown.__name__)
print('Docstring:', repr(countdown.__doc__))
print('Annotations:', countdown.__annotations__)
6.2 定義一個可接受參數(shù)的裝飾器
from functools import wraps
import logging
def logged(level, name=None, message=None):
'''
Add logging to a function. level is the logging
level, name is the logger name, and message is the
log message. If name and message aren't specified,
they default to the function's module and name.
'''
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate
# Example use
@logged(logging.DEBUG)
def add(x, y):
return x + y
@logged(logging.CRITICAL, 'example')
def spam():
print('Spam!')
if __name__ == '__main__':
import logging
logging.basicConfig(level=logging.DEBUG)
print(add(2,3))
spam()
6.3 定義一個能接受可選參數(shù)的裝飾器
from functools import wraps, partial
import logging
def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__
@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
# Example use
@logged
def add(x, y):
return x + y
@logged()
def sub(x, y):
return x - y
@logged(level=logging.CRITICAL, name='example')
def spam():
print('Spam!')
if __name__ == '__main__':
import logging
logging.basicConfig(level=logging.DEBUG)
add(2,3)
sub(2,3)
spam()
6.4 把裝飾器定義成類
要把裝飾器定義成類實例,需要確保在類中實現(xiàn)__call__()和__get__()方法把鉴。
# Example of defining a decorator as a class
import types
from functools import wraps
class Profiled:
def __init__(self, func):
wraps(func)(self)
self.ncalls = 0
def __call__(self, *args, **kwargs):
self.ncalls += 1
return self.__wrapped__(*args, **kwargs)
def __get__(self, instance, cls):
if instance is None:
return self
else:
return types.MethodType(self, instance)
# Example
@Profiled
def add(x, y):
return x + y
class Spam:
@Profiled
def bar(self, x):
print(self, x)
if __name__ == '__main__':
print(add(2,3))
print(add(4,5))
print('ncalls:', add.ncalls)
s = Spam()
s.bar(1)
s.bar(2)
s.bar(3)
print('ncalls:', Spam.bar.ncalls)
# Reformulation using closures and function attributes
# This example tests the composability of decorators
import time
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
r = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return r
return wrapper
def profiled(func):
ncalls = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal ncalls
ncalls += 1
return func(*args, **kwargs)
wrapper.ncalls = lambda: ncalls
return wrapper
# Example
@profiled
def add(x, y):
return x + y
class Spam:
@profiled
def bar(self, x):
print(self, x)
@timethis
@profiled
def countdown(n):
while n > 0:
n -= 1
if __name__ == '__main__':
print(add(2,3))
print(add(4,5))
print('ncalls:', add.ncalls())
s = Spam()
s.bar(1)
s.bar(2)
s.bar(3)
print('ncalls:', Spam.bar.ncalls())
countdown(100000)
countdown(10000000)
print(countdown.ncalls())
6.5 利用元類來控制實例的創(chuàng)建
# example2.py
#
# Singleton
class Singleton(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
class Spam(metaclass=Singleton):
def __init__(self):
print('Creating Spam')
if __name__ == '__main__':
a = Spam()
b = Spam()
print(a is b)