字符串格式化調(diào)用方法 —— format
通過創(chuàng)建字符串模板,利用format函數(shù),替代相應(yīng)的值。
可以通過絕對(duì)位置、相對(duì)位置以及關(guān)鍵字進(jìn)行替代。例如
# 字符串格式化調(diào)用方法
template = '{0}, {1} and {2}' # By position
print(template.format('spam', 'ham', 'eggs'))
#spam, ham and eggs
template = '{motto}, {pork} and {food}' # By keyword
print(template.format(motto='spam', pork='ham', food='eggs'))
#spam, ham and eggs
template = '{}, {} and {}' # By relative position
print(template.format('spam', 'ham', 'eggs')) # New in 3.1 and 2.7
#'spam, ham and eggs'
print('{motto}, {0} and {food}'.format(42, motto=3.14, food=[1, 2]))
# 3.14, 42 and [1, 2]
可以看到,food是一個(gè)列表,它會(huì)根據(jù)__str__()進(jìn)行替代。
格式化調(diào)用的高級(jí)用途
格式化字符串指定對(duì)象屬性(點(diǎn)表示)和字典鍵。例如
print('My {map[kind]} runs {sys.platform}'.format(sys=sys, map={'kind': 'laptop'}))
#My laptop runs win32
形式化結(jié)構(gòu)
{fieldname|conversionflag:formatspec}
- fieldname是指定參數(shù)的一個(gè)數(shù)字或關(guān)鍵字
- conversionflag可以是r、s,或者a,分別是該值上對(duì)repr、str或ascii內(nèi)置函數(shù)的一次調(diào)用
- formatspce指定了如何表示該值,包括字段寬度、對(duì)齊方式、補(bǔ)零、小數(shù)點(diǎn)精度等細(xì)節(jié),可以表示成
[fill|align][sign][#][0][width][.precision][typecode]
例如align可能是<,>或=,分別表示左對(duì)齊、右對(duì)齊或居中對(duì)齊。
例如
print('{0.platform:>10} = {1[kind]:<10}'.format(sys, dict(kind='laptop')))
# win32 = laptop
print('{0:f}, {1:.2f}, {2:06.2f}'.format(3.14159, 3.14159, 3.14159))
#3.141590, 3.14, 003.14
if/else三元表達(dá)式
例子
A = 't' if 'spam' else 'f'
print(A)
#t
迭代器
可迭代對(duì)象,基本上,就是序列觀念的通用化。
Python中所謂的迭代協(xié)議:有__next__方法的對(duì)象會(huì)前進(jìn)到下一個(gè)結(jié)果,而在一系列結(jié)果的末尾時(shí),則會(huì)引發(fā)StopIteration。在Python中,任何這類對(duì)象都認(rèn)為是可迭代的。任何這類對(duì)象也能以for循環(huán)或其他迭代工具遍歷,因?yàn)樗械ぞ邇?nèi)部工作起來都是在每次迭代中調(diào)用__next__,并且捕捉StopIteration異常來確定合適離開。
L = [1,2,3]
I = iter(L)
print(I.__next__())
#1
print(I.__next__())
#2
print(next(I))
#3
print(next(I))
#Traceback (most recent call last):
#StopIteration
自動(dòng)迭代和手動(dòng)迭代
L = [1, 2, 3]
for X in L: # Automatic iteration
print(X ** 2, end=' ') # Obtains iter, calls __next__, catches exceptions
#1 4 9
I = iter(L) # Manual iteration: what for loops usually do
while True:
try: # try statement catches exceptions
X = next(I) # Or call I.__next__ in 3.X
except StopIteration:
break
print(X ** 2, end=' ')
#1 4 9
多個(gè)迭代器和單個(gè)迭代器
多個(gè)迭代器,例如range,它不是自己的迭代器(手動(dòng)迭代時(shí),需要使用iter產(chǎn)生一個(gè)迭代器)。它支持在其結(jié)果上的多個(gè)迭代器,這些迭代器會(huì)記住它們各自的位置。與之相反,例如zip,它不支持多個(gè)迭代器。
R = range(3) # range allows multiple iterators
next(R)
#TypeError: 'range' object is not an iterator
I1 = iter(R)
print(next(I1))
#0
print(next(I1))
#1
I2 = iter(R)
print(next(I2))
#0
Z = zip((1, 2, 3), (10, 11, 12))
I1 = iter(Z)
I2 = iter(Z) # Two iterators on one zip
print(next(I1))
#(1, 10)
print(next(I1))
#(2, 11)
print(next(I2)) # (3.X) I2 is at same spot as I1!
#(3, 12)
變量域

內(nèi)置作用域僅僅是一個(gè)名為builtins的內(nèi)置模塊,但是必須要導(dǎo)入(即import builtins)之后才能使用內(nèi)置作用域。
import builtins
print(dir(builtins))
閉合(closure)或工廠函數(shù)
一個(gè)能夠記住嵌套作用域的變量值的函數(shù),盡管那個(gè)作用域已經(jīng)不存在了。
其實(shí),閉包指延伸了作用域的函數(shù),其中包含函數(shù)定義體中引用、但是不在定義體中定義的非全局變量。函數(shù)是不是匿名的沒有關(guān)系,關(guān)鍵是它能訪問定義體之外定義的非全局變量。
def maker(N):
def action(X): # Make and return action
return X ** N # action retains N from enclosing scope
return action
f = maker(2)
print(f)
#<function maker.<locals>.action at 0x0000019C3FCE6620>
print(f(3))
#9
這是函數(shù)式編程常用的方式。
在大多數(shù)情況下,給內(nèi)層的lambda函數(shù)通過默認(rèn)參數(shù)傳遞值沒什么必要。例如
def func1():
x = 4
action = (lambda n: x ** n) # x remembered from enclosing def
return action
def func2():
x = 4
action = (lambda n, x=x: x ** n) # Pass x in manually
return action
f1 = func1()
f2 = func2()
print(f1(2))
#16
print(f2(2))
#16
但是,如果嵌套在一個(gè)循環(huán)中,并且嵌套的函數(shù)引用了一個(gè)上層作用域的變量,該變量被循環(huán)所改變,所有在這個(gè)循環(huán)中產(chǎn)生的函數(shù)將會(huì)有相同的值——在最后一次循環(huán)中完成時(shí)被引用變量的值。
def makeActions():
acts = []
for i in range(5): # Tries to remember each i
acts.append(lambda x: i ** x) # But all remember same last i!
return acts
acts = makeActions()
print(acts[0](2))
#16
print(acts[1](2))
#16
def makeActions():
acts = []
for i in range(5): # Tries to remember each i
acts.append(lambda x, i=i: i ** x) # But all remember same last i!
return acts
acts = makeActions()
print(acts[0](2))
#0
print(acts[1](2))
#1
例子:average.py:計(jì)算移動(dòng)平均值的高階函數(shù)
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total/len(series)
return averager
測(cè)試示例
>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

例子:一個(gè)簡(jiǎn)單的裝飾器,輸出函數(shù)的運(yùn)行時(shí)間
import time
def clock(func):
def clocked(*args):
t0 = time.time()
result = func(*args)
elapsed = time.time() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
使用 clock 裝飾器
import time
from clockdeco import clock
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))
輸出結(jié)果如下
$ python3 clockdeco_demo.py
**************************************** Calling snooze(123)
[0.12405610s] snooze(.123) -> None
**************************************** Calling factorial(6)
[0.00000191s] factorial(1) -> 1
[0.00004911s] factorial(2) -> 2
[0.00008488s] factorial(3) -> 6
[0.00013208s] factorial(4) -> 24
[0.00019193s] factorial(5) -> 120
[0.00026107s] factorial(6) -> 720
6! = 720
上述實(shí)現(xiàn)的 clock 裝飾器有幾個(gè)缺點(diǎn):不支持關(guān)鍵字參數(shù),而且遮蓋了被裝飾函數(shù)的 __name__ 和 __doc__ 屬性。我們使用functools.wraps 裝飾器把相關(guān)的屬性從 func 復(fù)制到 clocked 中。此外,這個(gè)新版還能正確處理關(guān)鍵字參數(shù)。
例子:改進(jìn)后的 clock 裝飾器
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
return result
return clocked
nonlocal語句
nonlocal語句只在一個(gè)函數(shù)內(nèi)有意義。當(dāng)執(zhí)行nonlocal語句的時(shí)候,nonlocal中列出的名稱必須在一個(gè)嵌套的def中提前定義過,否則將會(huì)產(chǎn)生一個(gè)錯(cuò)誤。
def tester(start):
state = start # Each call gets its own state
def nested(label):
nonlocal state # Remembers state in enclosing scope
print(label, state)
state += 1 # Allowed to change it if nonlocal
return nested
F = tester(0)
F('spam')
#spam 0
F('eggs')
#eggs 1
例子:計(jì)算移動(dòng)平均值的高階函數(shù),不保存所有歷史值,但有
缺陷
def make_averager():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'count' referenced before assignment
問題是,當(dāng) count 是數(shù)字或任何不可變類型時(shí),count += 1 語句的作用其實(shí)與 count = count + 1 一樣。因此,我們?cè)?averager 的定義體中為 count 賦值了,這會(huì)把 count 變成局部變量。total 變量也受這個(gè)問題影響。
之前的示例沒遇到這個(gè)問題,因?yàn)槲覀儧]有給 series 賦值,我們只是調(diào)用 series.append,并把它傳給 sum 和 len。也就是說,我們利用了列表是可變的對(duì)象這一事實(shí)。
但是對(duì)數(shù)字、字符串、元組等不可變類型來說,只能讀取,不能更新。如果嘗試重新綁定,例如 count = count + 1,其實(shí)會(huì)隱式創(chuàng)建局部變量 count。這樣,count 就不是自由變量了,因此不會(huì)保存在閉包中。
為了解決這個(gè)問題,Python 3 引入了 nonlocal 聲明。它的作用是把變量標(biāo)記為自由變量,即使在函數(shù)中為變量賦予新值了,也會(huì)變成自由變量。
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
標(biāo)準(zhǔn)庫中的裝飾器
- 使用functools.lru_cache做備忘
functools.lru_cache 是非常實(shí)用的裝飾器,它實(shí)現(xiàn)了備忘(memoization)功能。這是一項(xiàng)優(yōu)化技術(shù),它把耗時(shí)的函數(shù)的結(jié)果保存起來,避免傳入相同的參數(shù)時(shí)重復(fù)計(jì)算。
例子:使用緩存實(shí)現(xiàn)生成第 n 個(gè)斐波納契數(shù)
import functools
#注意,必須像常規(guī)函數(shù)那樣調(diào)用 lru_cache。這一行中有一對(duì)括號(hào):@functools.lru_cache()。
@functools.lru_cache()
#這里疊放了裝飾器:@lru_cache() 應(yīng)用到 @clock 返回的函數(shù)上。
@clock
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-2) + fibonacci(n-1)
fibonacci(6)
#[0.00000000s] fibonacci(0) -> 0
#[0.00000000s] fibonacci(1) -> 1
#[0.00000000s] fibonacci(2) -> 1
#[0.00000000s] fibonacci(3) -> 2
#[0.00000000s] fibonacci(4) -> 3
#[0.00000000s] fibonacci(5) -> 5
#[0.00000000s] fibonacci(6) -> 8
- 單分派泛函數(shù) —— functools.singledispatch
使用@singledispatch 裝飾的普通函數(shù)會(huì)變成泛函數(shù)(generic function):根據(jù)第一個(gè)參數(shù)的類型,以不同方式執(zhí)行相同操作的一組函數(shù)。
from decimal import Decimal
from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
if verbose:
print("Let me just say,", end=" ")
print(arg)
@fun.register(int)
def _(arg, verbose=False):
if verbose:
print("Strength in numbers, eh?", end=" ")
print(arg)
@fun.register(list)
def _(arg, verbose=False):
if verbose:
print("Enumerate this:")
for i, elem in enumerate(arg):
print(i, elem)
@fun.register(float)
@fun.register(Decimal)
def fun_num(arg, verbose=False):
if verbose:
print("Half of your number:", end=" ")
print(arg / 2)
def nothing(arg, verbose=False):
print("Nothing.")
fun.register(type(None), nothing)
fun("Hello, world.")
#Hello, world.
fun("test.", verbose=True)
#Let me just say, test.
fun(42, verbose=True)
#Strength in numbers, eh? 42
fun(['spam', 'spam', 'eggs', 'spam'], verbose=True)
#Enumerate this:
#0 spam
#1 spam
#2 eggs
#3 spam
fun(None)
#Nothing.
fun(1.23)
#0.615
參數(shù)化裝飾器
創(chuàng)建一個(gè)裝飾器工廠函數(shù),把參數(shù)傳給它,返回一個(gè)裝飾器,然后再把它應(yīng)用到要裝飾的函數(shù)上。
例子:參數(shù)化clock裝飾器(為了簡(jiǎn)單起見,基于最初實(shí)現(xiàn)的clock)
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
#clock 是參數(shù)化裝飾器工廠函數(shù)
def clock(fmt=DEFAULT_FMT):
#decorate 是真正的裝飾器
def decorate(func):
#clocked 包裝被裝飾的函數(shù)
def clocked(*_args):
t0 = time.time()
#_result 是被裝飾的函數(shù)返回的真正結(jié)果
_result = func(*_args)
elapsed = time.time() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
#這里使用 **locals() 是為了在 fmt 中引用 clocked 的局部變量
print(fmt.format(**locals()))
#clocked 會(huì)取代被裝飾的函數(shù),因此它應(yīng)該返回被裝飾的函數(shù)返回的值
return _result
return clocked
return decorate
@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
time.sleep(seconds)
for i in range(3):
snooze(.123)
#snooze(0.123) dt=0.124s
#snooze(0.123) dt=0.124s
#snooze(0.123) dt=0.124s
遞歸函數(shù)
def sumtree(L):
tot = 0
for x in L: # For each item at this level
if not isinstance(x, list):
tot += x # Add numbers directly
else:
tot += sumtree(x) # Recur for sublists
return tot
L = [1, [2, [3, 4], 5], 6, [7, 8]] # Arbitrary nesting
print(sumtree(L)) # Prints 36
函數(shù)內(nèi)省
由于函數(shù)是對(duì)象,我們可以用常規(guī)的對(duì)象工具來處理函數(shù)。內(nèi)省工具允許我們探索實(shí)現(xiàn)的細(xì)節(jié)。
def func(a):
b = 'spam'
return b * a
print(func.__name__)
print(dir(func))
reduce函數(shù)
它接受一個(gè)迭代器來處理,返回一個(gè)單個(gè)的結(jié)果。
from functools import reduce
print(reduce((lambda x, y: x + y), [1, 2, 3, 4]))
#10
def myreduce(function, sequence):
tally = sequence[0]
for next in sequence[1:]:
tally = function(tally, next)
return tally
print(myreduce((lambda x, y: x + y), [1, 2, 3, 4, 5]))
#15
生成器
- 生成器函數(shù)
- 生成器表達(dá)式
生成器函數(shù)
它與常規(guī)函數(shù)之間的主要代碼不同在于,生成器yields一個(gè)值,而不是返回一個(gè)值。yield語句掛起該函數(shù)并向調(diào)用者發(fā)送回一個(gè)值,但是,保留足夠的狀態(tài)以使得函數(shù)能夠從它離開的地方繼續(xù)。當(dāng)繼續(xù)時(shí),函數(shù)在上一個(gè)yield返回后立即繼續(xù)執(zhí)行。
可迭代的對(duì)象定義了一個(gè)__next__方法,它要么返回迭代中的下一項(xiàng),或者引發(fā)一個(gè)特殊的StopIteration異常來終止迭代。一個(gè)對(duì)象的迭代器用iter內(nèi)置函數(shù)接收。生成器函數(shù),編寫為包含yield語句的def語句,自動(dòng)地支持迭代協(xié)議。
生成器函數(shù)和生成器表達(dá)式自身都是迭代器,因此是單迭代對(duì)象。
包導(dǎo)入模式

類的內(nèi)省工具
一個(gè)簡(jiǎn)單的內(nèi)省工具
class AttrDisplay:
"""
Provides an inheritable display overload method that shows
instances with their class names and a name=value pair for
each attribute stored on the instance itself (but not attrs
inherited from its classes). Can be mixed into any class,
and will work on any instance.
"""
def gatherAttrs(self):
attrs = []
for key in sorted(self.__dict__):
attrs.append('%s=%s' % (key, getattr(self, key)))
return ', '.join(attrs)
def __repr__(self):
return '[%s: %s]' % (self.__class__.__name__, self.gatherAttrs())
class TopTest(AttrDisplay):
count = 0
def __init__(self):
self.attr1 = TopTest.count
self.attr2 = TopTest.count+1
TopTest.count += 2
class SubTest(TopTest):
pass
X, Y = TopTest(), SubTest() # Make two instances
print(X) # Show all instance attrs
#[TopTest: attr1=0, attr2=1]
print(Y) # Show lowest class name
#[SubTest: attr1=2, attr2=3]
列出類樹中每個(gè)對(duì)象的屬性
顯示根據(jù)屬性所在的類來分組的屬性,它遍歷了整個(gè)類樹,在此過程中顯示了附加到每個(gè)對(duì)象上的屬性。
它這個(gè)遍歷繼承樹:從一個(gè)實(shí)例的__class__到其類,然后遞歸地從類的_bases__到其所有超類,一路掃描對(duì)象的__dict__。
def tester(listerclass, sept=False):
class Super:
def __init__(self): # Superclass __init__
self.data1 = 'spam' # Create instance attrs
def ham(self):
pass
class Sub(Super, listerclass): # Mix in ham and a __str__
def __init__(self): # Listers have access to self
Super.__init__(self)
self.data2 = 'eggs' # More instance attrs
self.data3 = 42
def spam(self): # Define another method here
pass
instance = Sub() # Return instance with lister's __str__
print(instance) # Run mixed-in __str__ (or via str(x))
if sept: print('-' * 80)
class ListTree:
"""
Mix-in that returns an __str__ trace of the entire class tree and all
its objects' attrs at and above self; run by print(), str() returns
constructed string; uses __X attr names to avoid impacting clients;
recurses to superclasses explicitly, uses str.format() for clarity;
"""
def __attrnames(self, obj, indent):
spaces = ' ' * (indent + 1)
result = ''
for attr in sorted(obj.__dict__):
if attr.startswith('__') and attr.endswith('__'):
result += spaces + '{0}\n'.format(attr)
else:
result += spaces + '{0}={1}\n'.format(attr, getattr(obj, attr))
return result
def __listclass(self, aClass, indent):
dots = '.' * indent
if aClass in self.__visited:
return '\n{0}<Class {1}:, address {2}: (see above)>\n'.format(
dots,
aClass.__name__,
id(aClass))
else:
self.__visited[aClass] = True
here = self.__attrnames(aClass, indent)
above = ''
for super in aClass.__bases__:
above += self.__listclass(super, indent+4)
return '\n{0}<Class {1}, address {2}:\n{3}{4}{5}>\n'.format(
dots,
aClass.__name__,
id(aClass),
here, above,
dots)
def __str__(self):
self.__visited = {}
here = self.__attrnames(self, 0)
above = self.__listclass(self.__class__, 4)
return '<Instance of {0}, address {1}:\n{2}{3}>'.format(
self.__class__.__name__,
id(self),
here, above)
tester(ListTree)
上述結(jié)果顯示為
<Instance of Sub, address 2053627225200:
_ListTree__visited={}
data1=spam
data2=eggs
data3=42
....<Class Sub, address 2053625967896:
__doc__
__init__
__module__
spam=<function tester.<locals>.Sub.spam at 0x000001DE25B91AE8>
........<Class Super, address 2053625972616:
__dict__
__doc__
__init__
__module__
__weakref__
ham=<function tester.<locals>.Super.ham at 0x000001DE25B919D8>
............<Class object, address 1391801792:
__class__
__delattr__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
............>
........>
........<Class ListTree, address 2053625973560:
_ListTree__attrnames=<function ListTree.__attrnames at 0x000001DE25B917B8>
_ListTree__listclass=<function ListTree.__listclass at 0x000001DE25B91840>
__dict__
__doc__
__module__
__str__
__weakref__
............<Class object:, address 1391801792: (see above)>
........>
....>
>
類接口技術(shù)
class Super:
def method(self):
print('in Super.method') # Default behavior
def delegate(self):
self.action() # Expected to be defined
class Inheritor(Super): # Inherit method verbatim
pass
class Replacer(Super): # Replace method completely
def method(self):
print('in Replacer.method')
class Extender(Super): # Extend method behavior
def method(self):
print('starting Extender.method')
Super.method(self)
print('ending Extender.method')
class Provider(Super): # Fill in a required method
def action(self):
print('in Provider.action')
for klass in (Inheritor, Replacer, Extender):
print('\n' + klass.__name__ + '...')
klass().method()
print('\nProvider...')
x = Provider()
x.delegate()
#Inheritor...
#in Super.method
#Replacer...
#in Replacer.method
#Extender...
#starting Extender.method
#in Super.method
#ending Extender.method
#Provider...
#in Provider.action
用戶定義迭代器
Python中所有的迭代環(huán)境都會(huì)先嘗試__iter__方法,再嘗試__getitem__。
從技術(shù)角度來說,迭代環(huán)境是通過調(diào)用內(nèi)置函數(shù)iter去嘗試尋找__iter__方法來實(shí)現(xiàn),而這種方法應(yīng)該返回一個(gè)迭代器對(duì)象。如果已經(jīng)提供了,Python就會(huì)重復(fù)調(diào)用這個(gè)迭代器對(duì)象的next方法,直到發(fā)生StopIteration異常。如果沒有找到這類__iter__方法,Python會(huì)改用__getitem__機(jī)制,就像之前那樣通過偏移量重復(fù)索引,直到引發(fā)InderError異常。
class Squares:
def __init__(self, start, stop): # Save state when created
self.value = start - 1
self.stop = stop
def __iter__(self): # Get iterator object on iter
return self
def __next__(self): # Return a square on each iteration
if self.value == self.stop: # Also called by next built-in
raise StopIteration
self.value += 1
return self.value ** 2
for i in Squares(1,5):
print(i, end=' ')
#1 4 9 16 25
上述的列子里,迭代器對(duì)象就是實(shí)例self,self里實(shí)現(xiàn)了__next__方法。要注意的是__iter__只循環(huán)一次,而不是多次。
sq = Squares(1,5)
print([n for n in sq])
#[1, 4, 9, 16, 25]
print([n for n in sq])
#[]
屬性引用:__getattr__和__setattr__
__getattr__方法是攔截未定義(不存在)屬性點(diǎn)號(hào)運(yùn)算。如果Python可通過其繼承樹搜索流程找到這個(gè)屬性,該方法就不會(huì)被調(diào)用。
class Empty:
def __getattr__(self, attrname): # On self.undefined
if attrname == 'age':
return 40
else:
raise AttributeError(attrname)
X = Empty()
print(X.age)
#40
而__setattr__會(huì)攔截所有屬性的賦值語句。如果定義了這個(gè)方法,self.attr=value會(huì)變成self.__setattr__('attr',value)。這一點(diǎn)技巧性很高,因?yàn)樵赺_setattr__中對(duì)任何self屬性做賦值,都會(huì)再調(diào)用__setattr__,導(dǎo)致了無窮遞歸循環(huán)。如果想要使用這個(gè)方法,要確定是通過對(duì)屬性字典做索引運(yùn)算來賦值,也就是說,是使用self.__dict__['name'] = x,而不是self.name=x。
class Accesscontrol:
def __setattr__(self, attr, value):
if attr == 'age':
self.__dict__[attr] = value + 10 # Not self.name=val or setattr
else:
raise AttributeError(attr + ' not allowed')
X = Accesscontrol()
X.age = 40
print(X.age)
#50
委托(delegation)
委托通常是以__getattr__鉤子方法實(shí)現(xiàn)的。
class Wrapper:
def __init__(self, object):
self.wrapped = object # Save object
def __getattr__(self, attrname):
print('Trace: ' + attrname) # Trace fetch
return getattr(self.wrapped, attrname) # Delegate fetch
x = Wrapper([1,2,3])
x.append(4)
#Trace: append
print(x.wrapped)
#[1, 2, 3, 4]
slots實(shí)例
將字符串屬性名稱順序賦值為特殊的__slots__類屬性,能夠優(yōu)化內(nèi)存和速度性能。
使用slots的時(shí)候,實(shí)例通常沒有一個(gè)屬性字典。
class C:
__slots__ = ['a', 'b']
X = C()
X.a = 1
print(X.a)
#1
print(X.__dict__)
#AttributeError: 'C' object has no attribute '__dict__'
可以通過在__slots__中包含__dict__,仍然可以容納額外的屬性,從而考慮到一個(gè)屬性空間字典的需求。下面這個(gè)例子中,兩種儲(chǔ)存機(jī)制都用到了。
class D:
__slots__ = ['a', 'b', '__dict__'] # Name __dict__ to include one too
c = 3 # Class attrs work normally
def __init__(self):
self.d = 4 # d stored in __dict__, a is a slot
X = D()
X.a = 1
X.b = 2
print(X.d)
#4
print(X.c)
#3
for attr in list(getattr(X, '__dict__', [])) + getattr(X, '__slots__', []):
print(attr, '=>', getattr(X, attr))
#d => 4
#a => 1
#b => 2
#__dict__ => {'d': 4}
靜態(tài)方法和類方法
使用類方法統(tǒng)計(jì)每個(gè)類的實(shí)例
class Spam:
numInstances = 0
def count(cls): # Per-class instance counters
cls.numInstances += 1 # cls is lowest class above instance
def __init__(self):
self.count() # Passes self.__class__ to count
count = classmethod(count)
class Sub(Spam):
numInstances = 0
def __init__(self): # Redefines __init__
Spam.__init__(self)
class Other(Spam): # Inherits __init__
numInstances = 0
x = Spam()
y1, y2 = Sub(), Sub()
z1, z2, z3 = Other(), Other(), Other()
print(Spam.numInstances, Sub.numInstances, Other.numInstances)
#1 2 3
異常
異?;A(chǔ)——捕獲異常
在try語句內(nèi),自行捕捉異常。
def fetcher(obj, index):
return obj[index]
x = 'spam'
def catcher():
try:
fetcher(x, 4)
except IndexError:
print('got exception')
print('continuing')
catcher()
#got exception
#continuing
異常基礎(chǔ)——引發(fā)異常
要手動(dòng)觸發(fā)異常,直接執(zhí)行raise語句。
try:
raise IndexError
except IndexError:
print('got exception!')
#got exception!
終止行為
try/finally的組合,可以定義一定會(huì)在最后執(zhí)行時(shí)的收尾行為,無論try代碼塊中是否發(fā)生了異常。
try:
fetcher(x, 3)
finally:
print('after fetch!')
#after fetch!
try語句分句

with/as環(huán)境管理器

文件對(duì)象有環(huán)境管理器,可在with代碼塊后自動(dòng)關(guān)閉文件,無論是否引發(fā)異常。
with語句實(shí)際的工作方式
- 計(jì)算表達(dá)式,所得到的對(duì)象稱為環(huán)境管理器,它必須有__enter__和__exit__方法。
- 環(huán)境管理器的__enter__方法會(huì)被調(diào)用。如果as子句存在,其返回值會(huì)賦值為as子句中的變量,否則直接丟棄。
- 代碼塊中嵌套的代碼會(huì)執(zhí)行。
- 如果with代碼塊引發(fā)異常,__exit__(type, value, traceback)方法就會(huì)被調(diào)用。如果此方法返回值為假,則異常會(huì)重新引發(fā)。否則,異常會(huì)終止。正常情況下異常是應(yīng)該被重新引發(fā),這樣的話才能傳遞到with語句之外。
- 如果with代碼塊沒有引發(fā)異常,__exit__方法依然會(huì)被調(diào)用,其type、value以及traceback參數(shù)都會(huì)以None傳遞。
class TraceBlock:
def message(self, arg):
print('running ' + arg)
def __enter__(self):
print('starting with block')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is None:
print('exited normally\n')
else:
print('raise an exception! ' + str(exc_type))
return False # Propagate
with TraceBlock() as action:
action.message('test 1')
print('reached')
#starting with block
#running test 1
#reached
#exited normally
內(nèi)置Exception類
- BaseException—— 異常的頂級(jí)根類,它不能由用戶定義的類直接繼承。
- Exception —— 與應(yīng)用相關(guān)的異常的頂層根超類。這是BaseException的一個(gè)直接子類,并且是所有其他內(nèi)置異常的超類。
管理屬性
- __getattr__和__setattr__方法,把未定義的屬性獲取和所有的屬性賦值指向通用的處理器方法
- __getattribute__方法,把所有屬性獲取都指向一個(gè)泛型處理器方法
- property內(nèi)置函數(shù),把特定屬性訪問定位到get和set處理器函數(shù),也叫特性
- 描述符協(xié)議,把特定屬性訪問定位到具體任意get和set處理器方法
特性
特性協(xié)議允許我們把一個(gè)特定屬性的get和set操作指向我們提供的函數(shù)或方法,使得我們能夠插入在屬性訪問的時(shí)候自動(dòng)運(yùn)行代碼。
class Person:
def __init__(self, name):
self._name = name
@property
def name(self): # name = property(name)
"name property docs"
print('fetch...')
return self._name
@name.setter
def name(self, value): # name = name.setter(name)
print('change...')
self._name = value
@name.deleter
def name(self): # name = name.deleter(name)
print('remove...')
del self._name
bob = Person('Bob Smith') # bob has a managed attribute
print(bob.name) # Runs name getter (name 1)
#fetch...
#Bob Smith
bob.name = 'Robert Smith' # Runs name setter (name 2)
#change...
print(bob.name)
#fetch...
#Robert Smith
描述符
描述符提供了攔截屬性訪問的一種替代方法。實(shí)際上,特性是描述符的一種——從技術(shù)上講,property內(nèi)置函數(shù)只是創(chuàng)建一個(gè)特定類型的描述符的一種簡(jiǎn)化方式,而這種描述符在屬性訪問時(shí)運(yùn)行方法函數(shù)。
從功能上講,描述符協(xié)議允許我們把一個(gè)特定屬性的get和set操作指向我們提供的一個(gè)單獨(dú)類對(duì)象的方法:它們提供了一種方式來插入在訪問屬性的時(shí)候自動(dòng)運(yùn)行的代碼。
描述符作為獨(dú)立的類創(chuàng)建,它提供了一種更為通用的解決方案。

class Name:
"name descriptor docs"
def __get__(self, instance, owner):
print('fetch...')
return instance._name
def __set__(self, instance, value):
print('change...')
instance._name = value
def __delete__(self, instance):
print('remove...')
del instance._name
class Person:
def __init__(self, name):
self._name = name
name = Name() # Assign descriptor to attr
bob = Person('Bob Smith') # bob has a managed attribute
print(bob.name) # Runs Name.__get__
#fetch...
#Bob Smith
bob.name = 'Robert Smith' # Runs Name.__set__
#change...
print(bob.name)
#fetch...
#Robert Smith
描述符可以使用實(shí)例狀態(tài)和描述符狀態(tài),或者兩者的任何組合
- 描述符狀態(tài)用來管理內(nèi)部用于描述符工作的數(shù)據(jù)
- 實(shí)例狀態(tài)記錄了和客戶類相關(guān)的信息
class DescState: # Use descriptor state
def __init__(self, value):
self.value = value
def __get__(self, instance, owner): # On attr fetch
print('DescState get')
return self.value * 10
def __set__(self, instance, value): # On attr assign
print('DescState set')
self.value = value
# Client class
class CalcAttrs:
X = DescState(2) # Descriptor class attr
Y = 3 # Class attr
def __init__(self):
self.Z = 4 # Instance attr
obj = CalcAttrs()
print(obj.X, obj.Y, obj.Z) # X is computed, others are not
#DescState get
#20 3 4
obj.X = 5 # X assignment is intercepted
#DescState set
obj.Y = 6
obj.Z = 7
print(obj.X, obj.Y, obj.Z)
#DescState get
#50 6 7
__getattr__和__getattribute__
屬性獲取攔截表現(xiàn)為兩種形式,可用兩種不同的方法來編寫:
- __getattr__針對(duì)未定義的屬性運(yùn)行
- __getattribute__針對(duì)每個(gè)屬性,因此,當(dāng)使用它的時(shí)候,必須小心避免通過把屬性訪問傳遞給超類而導(dǎo)致遞歸循環(huán)

避免屬性攔截方法中的循環(huán)。要解決這個(gè)問題,把獲取指向一個(gè)更高的超類,而不是跳過這個(gè)層級(jí)的版本——object類總是一個(gè)超類,并且它在這里可以很好地起作用。
def __getattribute__(self, name):
x = object.__getattribute__(self, 'other') # Force higher to avoid me
對(duì)于__setattr__,情況是類似的。在這個(gè)方法內(nèi)賦值任何屬性,都會(huì)再次觸發(fā)__setattr__并創(chuàng)建一個(gè)類似的循環(huán)。要解決這個(gè)問題,把屬性作為實(shí)例的__dict__命名空間字典中的一個(gè)鍵賦值。這樣就避免了直接的屬性賦值。
def __setattr__(self, name, value):
self.__dict__['other'] = value # Use atttr dict to avoid me
盡管這種方法比較少用到,但__setattr__也可以把自己的屬性賦值傳遞給一個(gè)更高的超類而避免循環(huán),就像__getattribute__一樣。
def __setattr__(self, name, value):
object.__setattr__(self, 'other', value) # Force higher to avoid me
相反,我們不能使用__dict__技巧在__getattribute__中避免循環(huán)。
def __getattribute__(self, name):
x = self.__dict__['other'] # LOOPS!
獲取__dict__屬性本身會(huì)再次觸發(fā)__getattribute__,導(dǎo)致一個(gè)遞歸循環(huán)。很奇怪,但確實(shí)如此。
例子:比較兩種get方法
class GetAttr:
attr1 = 1
def __init__(self):
self.attr2 = 2
def __getattr__(self, attr): # On undefined attrs only
print('get: ' + attr) # Not on attr1: inherited from class
if attr == 'attr3': # Not on attr2: stored on instance
return 3
else:
raise AttributeError(attr)
X = GetAttr()
print(X.attr1)
#1
print(X.attr2)
#2
print(X.attr3)
#get: attr3
#3
print('-' * 20)
class GetAttribute(object): # (object) needed in 2.X only
attr1 = 1
def __init__(self):
self.attr2 = 2
def __getattribute__(self, attr): # On all attr fetches
print('get: ' + attr) # Use superclass to avoid looping here
if attr == 'attr3':
return 3
else:
return object.__getattribute__(self, attr)
X = GetAttribute()
print(X.attr1)
#get: attr1
#1
print(X.attr2)
#get: attr2
#2
print(X.attr3)
#get: attr3
#3
示例:屬性驗(yàn)證
- 方法一:使用特性來驗(yàn)證
要理解這段代碼,關(guān)鍵是要注意到,__init__構(gòu)造函數(shù)方法內(nèi)部的屬性賦值也觸發(fā)了特性的setter方法。例如,當(dāng)這個(gè)方法分配給self.name時(shí),它自動(dòng)調(diào)用setName方法,該方法轉(zhuǎn)換值并將其賦給一個(gè)叫做__name的實(shí)例屬性,以便它不會(huì)與特性的名稱沖突。
class CardHolder:
acctlen = 8 # Class data
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct # Instance data
self.name = name # These trigger prop setters too!
self.age = age # __X mangled to have class name
self.addr = addr # addr is not managed
# remain has no data
def getName(self):
return self.__name
def setName(self, value):
value = value.lower().replace(' ', '_')
self.__name = value
name = property(getName, setName)
def getAge(self):
return self.__age
def setAge(self, value):
if value < 0 or value > 150:
raise ValueError('invalid age')
else:
self.__age = value
age = property(getAge, setAge)
def getAcct(self):
return self.__acct[:-3] + '***'
def setAcct(self, value):
value = value.replace('-', '')
if len(value) != self.acctlen:
raise TypeError('invald acct number')
else:
self.__acct = value
acct = property(getAcct, setAcct)
def remainGet(self): # Could be a method, not attr
return self.retireage - self.age # Unless already using as attr
remain = property(remainGet)
def printholder(who):
print(who.acct, who.name, who.age, who.remain, who.addr, sep=' / ')
bob = CardHolder('1234-5678', 'Bob Smith', 40, '123 main st')
printholder(bob)
#12345*** / bob_smith / 40 / 19.5 / 123 main st
bob.name = 'Bob Q. Smith'
bob.age = 50
bob.acct = '23-45-67-89'
printholder(bob)
#23456*** / bob_q._smith / 50 / 9.5 / 123 main st
sue = CardHolder('5678-12-34', 'Sue Jones', 35, '124 main st')
printholder(sue)
#56781*** / sue_jones / 35 / 24.5 / 124 main st
try:
sue.age = 200
except:
print('Bad age for Sue')
#Bad age for Sue
try:
sue.remain = 5
except:
print("Can't set sue.remain")
#Can't set sue.remain
try:
sue.acct = '1234567'
except:
print('Bad acct for Sue')
#Bad acct for Sue
方法二:使用描述符驗(yàn)證
要理解這段代碼,注意__init__構(gòu)造函數(shù)方法內(nèi)部的屬性賦值觸發(fā)了描述符的__set__操作符方法,這一點(diǎn)還是很重要。例如,當(dāng)構(gòu)造函數(shù)方法分配給self.name時(shí),它自動(dòng)調(diào)用Name.__set__()方法,該方法轉(zhuǎn)換值,并且將其賦給了叫做name的一個(gè)描述符屬性。
class CardHolder:
acctlen = 8 # Class data
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct # Instance data
self.name = name # These trigger __set__ calls too!
self.age = age # __X not needed: in descriptor
self.addr = addr # addr is not managed
# remain has no data
class Name(object):
def __get__(self, instance, owner): # Class names: CardHolder locals
return self.name
def __set__(self, instance, value):
value = value.lower().replace(' ', '_')
self.name = value
name = Name()
class Age(object):
def __get__(self, instance, owner):
return self.age # Use descriptor data
def __set__(self, instance, value):
if value < 0 or value > 150:
raise ValueError('invalid age')
else:
self.age = value
age = Age()
class Acct(object):
def __get__(self, instance, owner):
return self.acct[:-3] + '***'
def __set__(self, instance, value):
value = value.replace('-', '')
if len(value) != instance.acctlen: # Use instance class data
raise TypeError('invald acct number')
else:
self.acct = value
acct = Acct()
class Remain(object):
def __get__(self, instance, owner):
return instance.retireage - instance.age # Triggers Age.__get__
def __set__(self, instance, value):
raise TypeError('cannot set remain') # Else set allowed here
remain = Remain()
def printholder(who):
print(who.acct, who.name, who.age, who.remain, who.addr, sep=' / ')
bob = CardHolder('1234-5678', 'Bob Smith', 40, '123 main st')
printholder(bob)
#12345*** / bob_smith / 40 / 19.5 / 123 main st
bob.name = 'Bob Q. Smith'
bob.age = 50
bob.acct = '23-45-67-89'
printholder(bob)
#23456*** / bob_q._smith / 50 / 9.5 / 123 main st
sue = CardHolder('5678-12-34', 'Sue Jones', 35, '124 main st')
printholder(sue)
#56781*** / sue_jones / 35 / 24.5 / 124 main st
try:
sue.age = 200
except:
print('Bad age for Sue')
#Bad age for Sue
try:
sue.remain = 5
except:
print("Can't set sue.remain")
#Can't set sue.remain
try:
sue.acct = '1234567'
except:
print('Bad acct for Sue')
#Bad acct for Sue
**方法三:使用__getattr__來驗(yàn)證
對(duì)于這個(gè)例子的特性和描述符版本,注意__init__構(gòu)造函數(shù)方法中的屬性賦值觸發(fā)了類的__setattr__方法,這還是很關(guān)鍵的。例如,當(dāng)這個(gè)方法分配給self.name時(shí),它自動(dòng)地調(diào)用__setattr__方法,該方法轉(zhuǎn)換值,并將其分配給一個(gè)名為name的實(shí)例屬性。通過在該實(shí)例上存儲(chǔ)name,它確保了未來的訪問不會(huì)觸發(fā)__getattr__。相反,acct存儲(chǔ)為_acct,因此隨后對(duì)acct的訪問會(huì)調(diào)用__getattr__。
class CardHolder:
acctlen = 8 # Class data
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct # Instance data
self.name = name # These trigger __setattr__ too
self.age = age # _acct not mangled: name tested
self.addr = addr # addr is not managed
# remain has no data
def __getattr__(self, name):
if name == 'acct': # On undefined attr fetches
return self._acct[:-3] + '***' # name, age, addr are defined
elif name == 'remain':
return self.retireage - self.age # Doesn't trigger __getattr__
else:
raise AttributeError(name)
def __setattr__(self, name, value):
if name == 'name': # On all attr assignments
value = value.lower().replace(' ', '_') # addr stored directly
elif name == 'age': # acct mangled to _acct
if value < 0 or value > 150:
raise ValueError('invalid age')
elif name == 'acct':
name = '_acct'
value = value.replace('-', '')
if len(value) != self.acctlen:
raise TypeError('invald acct number')
elif name == 'remain':
raise TypeError('cannot set remain')
self.__dict__[name] = value # Avoid looping (or via object)
方法四:使用__getattribute__驗(yàn)證
注意,由于每個(gè)屬性獲取都指向了__getattribute__,所以這里我們不需要壓縮名稱以攔截它們(acct存儲(chǔ)為acct)。另一方面,這段代碼必須負(fù)責(zé)把未壓縮的屬性獲取指向一個(gè)超類以避免循環(huán)。
class CardHolder:
acctlen = 8 # Class data
retireage = 59.5
def __init__(self, acct, name, age, addr):
self.acct = acct # Instance data
self.name = name # These trigger __setattr__ too
self.age = age # acct not mangled: name tested
self.addr = addr # addr is not managed
# remain has no data
def __getattribute__(self, name):
superget = object.__getattribute__ # Don't loop: one level up
if name == 'acct': # On all attr fetches
return superget(self, 'acct')[:-3] + '***'
elif name == 'remain':
return superget(self, 'retireage') - superget(self, 'age')
else:
return superget(self, name) # name, age, addr: stored
def __setattr__(self, name, value):
if name == 'name': # On all attr assignments
value = value.lower().replace(' ', '_') # addr stored directly
elif name == 'age':
if value < 0 or value > 150:
raise ValueError('invalid age')
elif name == 'acct':
value = value.replace('-', '')
if len(value) != self.acctlen:
raise TypeError('invald acct number')
elif name == 'remain':
raise TypeError('cannot set remain')
self.__dict__[name] = value # Avoid loops, orig names
裝飾器
裝飾是為函數(shù)和類指定管理代碼的一種方式。裝飾器本身的形式是處理其他的可調(diào)用對(duì)象的可調(diào)用的對(duì)象(如函數(shù))。
- 函數(shù)裝飾器
- 類裝飾器
簡(jiǎn)而言之,裝飾器提供了一種方法,在函數(shù)和類定義語句的末尾插入自動(dòng)運(yùn)行代碼——對(duì)于函數(shù)裝飾器,在def的末尾;對(duì)于類裝飾器,在class的末尾。這樣的代碼可以扮演不同的角色。
管理函數(shù)和類
- 函數(shù)裝飾器也可以用來管理函數(shù)對(duì)象,而不是隨后對(duì)它們的調(diào)用——例如,把一個(gè)函數(shù)注冊(cè)到一個(gè)API。
- 類裝飾器也可以用來直接管理類對(duì)象,而不是實(shí)例創(chuàng)建調(diào)用——例如,用新的方法擴(kuò)展類。
為了支持函數(shù)和方法,嵌套函數(shù)的替代方法工作得更好。

Python何時(shí)執(zhí)行裝飾器
裝飾器的一個(gè)關(guān)鍵特性是,它們?cè)诒谎b飾的函數(shù)定義之后立即運(yùn)行。這通常是在導(dǎo)入時(shí)(即 Python 加載模塊時(shí))。
例子:registration.py 模塊
registry = [] # registry will hold references to functions decorated by @register.
def register(func): # register takes a function as argument.
print('running register(%s)' % func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main(): # main displays the registry, then calls f1(), f2(), and f3().
print('running main()')
print('registry ->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()
把 registration.py 當(dāng)作腳本運(yùn)行得到的輸出如下
$ python3 registration.py
running register(<function f1 at 0x100631bf8>)
running register(<function f2 at 0x100631c80>)
running main()
registry -> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>]
running f1()
running f2()
running f3()
如果導(dǎo)入 registration.py 模塊(不作為腳本運(yùn)行),輸出如下
>>> import registration
running register(<function f1 at 0x10063b1e0>)
running register(<function f2 at 0x10063b268>)
函數(shù)裝飾器在導(dǎo)入模塊時(shí)立即執(zhí)行,而被裝飾的函數(shù)只在明確調(diào)用時(shí)運(yùn)行。這突出了 Python 程序員所說的導(dǎo)入時(shí)和運(yùn)行時(shí)之間的區(qū)別。
用bisect來管理已排序的序列
bisect 模塊包含兩個(gè)主要函數(shù),bisect 和 insort,兩個(gè)函數(shù)都利用二分查找算法來在有序序列中查找或插入元素。
例子:在有序序列中用 bisect 查找某個(gè)元素的插入位置
import bisect
HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
ROW_FMT = '{0:2d} @ {1:2d} {2}{0:<2d}'
def demo(bisect_fn):
for needle in reversed(NEEDLES):
# 用特定的 bisect 函數(shù)來計(jì)算元素應(yīng)該出現(xiàn)的位置。
position = bisect_fn(HAYSTACK, needle)
# 利用該位置來算出需要幾個(gè)分隔符號(hào)。
offset = position * ' |'
# 把元素和其應(yīng)該出現(xiàn)的位置打印出來。
print(ROW_FMT.format(needle, position, offset))
bisect_fn = bisect.bisect
#把選定的函數(shù)在抬頭打印出來。
print('DEMO:', bisect_fn.__name__)
print('haystack ->', ' '.join('%2d' % n for n in HAYSTACK))
demo(bisect_fn)
#DEMO: bisect
#haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
#31 @ 14 | | | | | | | | | | | | | |31
#30 @ 14 | | | | | | | | | | | | | |30
#29 @ 13 | | | | | | | | | | | | |29
#23 @ 11 | | | | | | | | | | |23
#22 @ 9 | | | | | | | | |22
#10 @ 5 | | | | |10
# 8 @ 5 | | | | |8
# 5 @ 3 | | |5
# 2 @ 1 |2
# 1 @ 1 |1
# 0 @ 0 0
bisect 函數(shù)其實(shí)是 bisect_right 函數(shù)的別名,后者還有個(gè)姊妹函數(shù)叫 bisect_left。它們的區(qū)別在于,bisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,也就是新元素會(huì)被放置于它相等的元素的前面,而 bisect_right 返回的則是跟它相等的元素之后的位置。
bisect 可以用來建立一個(gè)用數(shù)字作為索引的查詢表格,比如說把分?jǐn)?shù)
和成績(jī)。
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
i = bisect.bisect(breakpoints, score)
return grades[i]
print([grade(score) for score in [33, 99, 77, 70, 89, 90, 100]])
#['F', 'A', 'C', 'C', 'B', 'A', 'A']
排序很耗時(shí),因此在得到一個(gè)有序序列之后,我們最好能夠保持它的有序。bisect.insort 就是為了這個(gè)而存在的。insort(seq, item) 把變量 item 插入到序列 seq 中,并能保持 seq的升序順序。
例子:insort 可以保持有序序列的順序
import random
SIZE = 7
random.seed(1729)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE*2)
bisect.insort(my_list, new_item)
print('%2d ->' % new_item, my_list)
#10 -> [10]
# 0 -> [0, 10]
# 6 -> [0, 6, 10]
# 8 -> [0, 6, 8, 10]
# 7 -> [0, 6, 7, 8, 10]
# 2 -> [0, 2, 6, 7, 8, 10]
#10 -> [0, 2, 6, 7, 8, 10, 10]
數(shù)組
如果我們需要一個(gè)只包含數(shù)字的列表,那么 array.array 比 list 更高效。數(shù)組支持所有跟可變序列有關(guān)的操作,包括 .pop、.insert 和.extend。另外,數(shù)組還提供從文件讀取和存入文件的更快的方法,如.frombytes 和 .tofile。
from array import array
floats = array('d', (random.random() for i in range(10**7)))
print(floats[-1])
#0.5963321947530882
內(nèi)存視圖
memoryview 是一個(gè)內(nèi)置類,它能讓用戶在不復(fù)制內(nèi)容的情況下操作同一個(gè)數(shù)組的不同切片。
>>> numbers = array.array('h', [-2, -1, 0, 1, 2])
>>> memv = memoryview(numbers)
>>> len(memv)
5
>>> memv[0]
-2
>>> memv_oct = memv.cast('B')
>>> memv_oct.tolist()
[254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
>>> memv_oct[5] = 4
隊(duì)列
collections.deque 類(雙向隊(duì)列)是一個(gè)線程安全、可以快速從兩端添加或者刪除元素的數(shù)據(jù)類型。而且如果想要有一種數(shù)據(jù)類型來存放“最近用到的幾個(gè)元素”,deque 也是一個(gè)很好的選擇。
>>> from collections import deque
>>> dq = deque(range(10), maxlen=10)
>>> dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.rotate(3)
>>> dq
deque([7, 8, 9, 0, 1, 2, 3, 4, 5, 6], maxlen=10)
>>> dq.rotate(-4)
>>> dq
deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 0], maxlen=10)
>>> dq.appendleft(-1)
>>> dq
deque([-1, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10)
>>> dq.extend([11, 22, 33])
>>> dq
deque([3, 4, 5, 6, 7, 8, 9, 11, 22, 33], maxlen=10)
>>> dq.extendleft([10, 20, 30, 40])
>>> dq
deque([40, 30, 20, 10, 3, 4, 5, 6, 7, 8], maxlen=10)
可散列的數(shù)據(jù)類型
如果一個(gè)對(duì)象是可散列的,那么在這個(gè)對(duì)象的生命周期中,它的散列值是不變的,而且這個(gè)對(duì)象需要實(shí)現(xiàn)__hash__() 方法。另外可散列對(duì)象還要有__qe__() 方法,這樣才能跟其他鍵做比較。如果兩個(gè)可散列對(duì)象是相等的,那么它們的散列值一定是一樣的。
原子不可變數(shù)據(jù)類型(str、bytes 和數(shù)值類型)都是可散列類
型,frozenset 也是可散列的。元組的話,只有當(dāng)一個(gè)元組包含的所有元素都是可散列類型的情況下,它才是可散列的。
處理字典中找不到的鍵
- 利用setdefault
my_dict.setdefault(key, []).append(new_value)
相當(dāng)于
if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)
- 利用defaultdict
from collections import defaultdict
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
d[k].append(v)
print(sorted(d.items()))
#[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
所有的映射類型在處理找不到的鍵的時(shí)候,都會(huì)牽扯到__missing__方法。
例子:當(dāng)有非字符串的鍵被查找的時(shí)候,StrKeyDict0 是如何
在該鍵不存在的情況下,把它轉(zhuǎn)換為字符串的
class StrKeyDict0(dict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __contains__(self, key):
return key in self.keys() or str(key) in self.keys()
>>> d = StrKeyDict0([('2', 'two'), ('4', 'four')])
>>> d['2']
'two'
>>> d[1]
Traceback (most recent call last):
...
KeyError: '1'
>>> d.get('2')
'two'
>>> d.get(4)
'four'
>>> d.get(1, 'N/A')
'N/A'
>>> 2 in d
True
>>> 1 in d
False
字典的變種
- collections.OrderedDict
- collections.ChainMap —— 該類型可以容納數(shù)個(gè)不同的映射對(duì)象,然后在進(jìn)行鍵查找操作的時(shí)候,這些對(duì)象會(huì)被當(dāng)作一個(gè)整體被逐個(gè)查找,直到鍵被找到為止。
import builtins
pylookup = ChainMap(locals(), globals(), vars(builtins))
- collections.Counter —— 這個(gè)映射類型會(huì)給鍵準(zhǔn)備一個(gè)整數(shù)計(jì)數(shù)器。每次更新一個(gè)鍵的時(shí)候都會(huì)增加這個(gè)計(jì)數(shù)器。
>>> ct = collections.Counter('abracadabra')
>>> ct
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.update('aaaaazzz')
>>> ct
Counter({'a': 10, 'z': 3, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
>>> ct.most_common(2)
[('a', 10), ('z', 3)]
- collections.UserDict —— 更傾向于從 UserDict 而不是從 dict 繼承的主要原因是,后者有時(shí)會(huì)在某些方法的實(shí)現(xiàn)上走一些捷徑,導(dǎo)致我們不得不在它的子類中重寫這些方法,但是 UserDict 就不會(huì)帶來這些問題。
import collections
class StrKeyDict(collections.UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
return str(key) in self.data
def __setitem__(self, key, item):
self.data[str(key)] = item
不可變映射類型:MappingProxyType
types 模塊中引入了一個(gè)封裝類名叫MappingProxyType。如果給這個(gè)類一個(gè)映射,它會(huì)返回一個(gè)只讀的映射視圖。雖然是個(gè)只讀視圖,但是它是動(dòng)態(tài)的。這意味著如果對(duì)原映射做出了改動(dòng),我們通過這個(gè)視圖可以觀察到,但是無法通過這個(gè)視圖對(duì)原映射做出修改。
>>> from types import MappingProxyType
>>> d = {1: 'A'}
>>> d_proxy = MappingProxyType(d)
>>> d_proxy
mappingproxy({1: 'A'})
>>> d_proxy[1]
'A'
>>> d_proxy[2] = 'x'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> d[2] = 'B'
>>> d_proxy
mappingproxy({1: 'A', 2: 'B'})
>>> d_proxy[2]
'B'
獲取關(guān)于函數(shù)參數(shù)的信息
使用 inspect 模塊
例子:clip函數(shù)
def clip(text, max_len=80):
"""Return text clipped at the last space before or after max_len
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None: # no spaces were found
end = len(text)
return text[:end].rstrip()
from inspect import signature
sig = signature(clip)
print(sig)
#(text, max_len=80)
for name, param in sig.parameters.items():
print(param.kind, ':', name, '=', param.default)
#POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
#POSITIONAL_OR_KEYWORD : max_len = 80
支持函數(shù)式編程的包——operator模塊
Python 的目標(biāo)不是變成函數(shù)式編程語言,但是得益于operator和functools等包的支持,函數(shù)式編程風(fēng)格也可以信手拈來。
operator 模塊為多個(gè)算術(shù)運(yùn)算符提供了對(duì)應(yīng)的函數(shù)。
例子:使用 reduce 和 operator.mul 函數(shù)計(jì)算階乘
from functools import reduce
from operator import mul
def fact(n):
return reduce(mul, range(1, n+1))
operator 模塊中還有一類函數(shù),能替代從序列中取出元素或讀取對(duì)象
屬性的 lambda 表達(dá)式:因此,itemgetter 和 attrgetter 其實(shí)會(huì)自行構(gòu)建函數(shù)。
例子:使用 itemgetter 排序一個(gè)元組列表
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
from operator import itemgetter
for city in sorted(metro_data, key=itemgetter(1)):
print(city)
#('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833))
#('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
#('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
#('Mexico City', 'MX', 20.142, (19.433333, -99.133333))
#('New York-Newark', 'US', 20.104, (40.808611, -74.020386))
methodcaller它的作用與 attrgetter 和 itemgetter 類似,它會(huì)自行創(chuàng)建函數(shù)。methodcaller 創(chuàng)建的函數(shù)會(huì)在對(duì)象上調(diào)用參數(shù)指定的方法。
>>> from operator import methodcaller
>>> s = 'The time has come'
>>> upcase = methodcaller('upper')
>>> upcase(s)
'THE TIME HAS COME'
>>> hiphenate = methodcaller('replace', ' ', '-')
>>> hiphenate(s)
'The-time-has-come'
支持函數(shù)式編程的包——functools模塊
functools 模塊提供了一系列高階函數(shù),其中最為人熟知的或許是
reduce。
functools.partial 這個(gè)高階函數(shù)用于部分應(yīng)用一個(gè)函數(shù)。部分應(yīng)用是指,基于一個(gè)函數(shù)創(chuàng)建一個(gè)新的可調(diào)用對(duì)象,把原函數(shù)的某些參數(shù)固定。使用這個(gè)函數(shù)可以把接受一個(gè)或多個(gè)參數(shù)的函數(shù)改編成需要回調(diào)的API,這樣參數(shù)更少。
>>> from operator import mul
>>> from functools import partial
>>> triple = partial(mul, 3)
>>> triple(7)
21
>>> list(map(triple, range(1, 10)))
[3, 6, 9, 12, 15, 18, 21, 24, 27]
classmethod與staticmethod
classmethod 最常見的用途是定義備選構(gòu)造方法。staticmethod靜態(tài)方法就是普通的函數(shù),只是碰巧在類的定義體中,而不是在模塊層定義。
標(biāo)準(zhǔn)庫中的抽象基類
ABC中的抽象基類



抽象基類的數(shù)字塔——numbers包
其中 Number 是位于最頂端的超類,隨后是 Complex 子類,依次往下,最底端是 Integral類:
- Number
- Complex
- Real
- Rational
- Integral
如果想檢查一個(gè)數(shù)是不是整數(shù),可以使用 isinstance(x, numbers.Integral),這樣代碼就能接受 int、bool(int 的子類)
接口:從協(xié)議到抽象基類
接口在動(dòng)態(tài)類型語言中是怎么運(yùn)作的呢?首先,基本的事實(shí)是,Python語言沒有 interface 關(guān)鍵字,而且除了抽象基類,每個(gè)類都有接口:類實(shí)現(xiàn)或繼承的公開屬性(方法或數(shù)據(jù)屬性),包括特殊方法,如__getitem__ 或 __add__。
協(xié)議是接口,但不是正式的(只由文檔和約定定義),因此協(xié)議不能像正式接口那樣施加限制。
序列協(xié)議是 Python 最基礎(chǔ)的協(xié)議之一。即便對(duì)象只實(shí)現(xiàn)了那個(gè)協(xié)議最基本的一部分,解釋器也會(huì)負(fù)責(zé)任地處理。
這里簡(jiǎn)單介紹序列接口的情況。

示例中的 Foo 類。它沒有繼承 abc.Sequence,而且只實(shí)現(xiàn)了序列協(xié)議的一個(gè)方法:__getitem__ (沒有實(shí)現(xiàn) __len__ 方法)。
例子:定義 __getitem__ 方法,只實(shí)現(xiàn)序列協(xié)議的一部分,這樣足夠訪問元素、迭代和使用 in 運(yùn)算符了。
class Foo:
def __getitem__(self, pos):
return range(0, 30, 10)[pos]
f = Foo()
for i in f:
print(i)
#0
#10
#20
print(20 in f)
#True
雖然沒有 __iter__ 方法,但是 Foo 實(shí)例是可迭代的對(duì)象,因?yàn)榘l(fā)現(xiàn)有__getitem__ 方法時(shí),Python 會(huì)調(diào)用它,傳入從 0 開始的整數(shù)索引,嘗試迭代對(duì)象(這是一種后備機(jī)制)。盡管沒有實(shí)現(xiàn) __contains__ 方法,但是 Python 足夠智能,能迭代 Foo 實(shí)例,因此也能使用 in 運(yùn)算符:Python 會(huì)做全面檢查,看看有沒有指定的元素。
綜上,鑒于序列協(xié)議的重要性,如果沒有 __iter__ 和 __contains__方法,Python 會(huì)調(diào)用 __getitem__ 方法,設(shè)法讓迭代和 in 運(yùn)算符可用。
另一個(gè)例子:實(shí)現(xiàn)序列協(xié)議的 FrenchDeck 類
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck:
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
猴子補(bǔ)丁
上述FrenchDeck 類有個(gè)重大缺陷:無法洗牌。
>>> from random import shuffle
>>> from frenchdeck import FrenchDeck
>>> deck = FrenchDeck()
>>> shuffle(deck)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File ".../python3.3/random.py", line 265, in shuffle
x[i], x[j] = x[j], x[i]
TypeError: 'FrenchDeck' object does not support item assignment
因?yàn)?,shuffle 函數(shù)要調(diào)換集合中元素的位置,而 FrenchDeck 只實(shí)現(xiàn)了不可變的序列協(xié)議??勺兊男蛄羞€必須提供 __setitem__ 方法。
例子:為FrenchDeck 打猴子補(bǔ)丁,把它變成可變的,讓
random.shuffle 函數(shù)能處理
from random import shuffle
deck = FrenchDeck()
def set_card(deck, position, card):
deck._cards[position] = card
FrenchDeck.__setitem__ = set_card
shuffle(deck)
print(deck[:5])
#[Card(rank='2', suit='spades'), Card(rank='9', suit='spades'), Card(rank='9', suit='hearts'), Card(rank='5', suit='diamonds'), Card(rank='3', suit='clubs')]
這里的關(guān)鍵是,set_card 函數(shù)要知道 deck 對(duì)象有一個(gè)名為 _cards 的屬性,而且 _cards 的值必須是可變序列。然后,我們把 set_card 函數(shù)賦值給特殊方法 __setitem__,從而把它依附到 FrenchDeck 類上。這種技術(shù)叫猴子補(bǔ)?。涸谶\(yùn)行時(shí)修改類或模塊,而不改動(dòng)源碼。
白鵝類型
白鵝類型指,只要 cls 是抽象基類,即 cls 的元類是abc.ABCMeta,就可以使用 isinstance(obj, cls)。
其實(shí),抽象基類的本質(zhì)就是幾個(gè)特殊方法。
然而,即便是抽象基類,也不能濫用 isinstance 檢查,用得多了可能導(dǎo)致代碼異味,即表明面向?qū)ο笤O(shè)計(jì)得不好。在一連串 if/elif/elif中使用 isinstance 做檢查,然后根據(jù)對(duì)象的類型執(zhí)行不同的操作,通常是不好的做法;此時(shí)應(yīng)該使用多態(tài),即采用一定的方式定義類,讓解釋器把調(diào)用分派給正確的方法,而不使用 if/elif/elif 塊硬編碼分派邏輯。
定義抽象基類的子類。例子:FrenchDeck2,collections.MutableSequence的子類
import collections
Card = collections.namedtuple('Card', ['rank', 'suit'])
class FrenchDeck2(collections.MutableSequence):
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = 'spades diamonds clubs hearts'.split()
def __init__(self):
self._cards = [Card(rank, suit) for suit in self.suits
for rank in self.ranks]
def __len__(self):
return len(self._cards)
def __getitem__(self, position):
return self._cards[position]
#為了支持洗牌,只需實(shí)現(xiàn)__setitem__方法
def __setitem__(self, position, value):
self._cards[position] = value
#但是繼承MutableSequence的類必須實(shí)現(xiàn)__delitem__方法,這是MutableSequence 類的一個(gè)抽象方法。
def __delitem__(self, position):
del self._cards[position]
#此外,還要實(shí)現(xiàn)insert方法,這是MutableSequence類的第三個(gè)抽象方法。
def insert(self, position, value):
self._cards.insert(position, value)
參考文獻(xiàn)
- Python學(xué)習(xí)手冊(cè)
- Python Cookbook
- Fluent Python