Python筆記

字符串格式化調(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)

變量域

image.png

內(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
image.png

例子:一個(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)入模式

image.png

類的內(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語句分句

image.png

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

image.png

文件對(duì)象有環(huán)境管理器,可在with代碼塊后自動(dòng)關(guān)閉文件,無論是否引發(fā)異常。

with語句實(shí)際的工作方式

  1. 計(jì)算表達(dá)式,所得到的對(duì)象稱為環(huán)境管理器,它必須有__enter__和__exit__方法。
  2. 環(huán)境管理器的__enter__方法會(huì)被調(diào)用。如果as子句存在,其返回值會(huì)賦值為as子句中的變量,否則直接丟棄。
  3. 代碼塊中嵌套的代碼會(huì)執(zhí)行。
  4. 如果with代碼塊引發(fā)異常,__exit__(type, value, traceback)方法就會(huì)被調(diào)用。如果此方法返回值為假,則異常會(huì)重新引發(fā)。否則,異常會(huì)終止。正常情況下異常是應(yīng)該被重新引發(fā),這樣的話才能傳遞到with語句之外。
  5. 如果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)建,它提供了一種更為通用的解決方案。

image.png
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)
image.png

避免屬性攔截方法中的循環(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ù)的替代方法工作得更好。

image.png

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ù),bisectinsort,兩個(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中的抽象基類

image.png
image.png
image.png

抽象基類的數(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)單介紹序列接口的情況。

image.png

示例中的 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
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容