python cookbook 自营_Python Cookbook总结 7-8 章
第八章和第九章的知識點超級多。。。建議這兩章直接看書復習就行了。。。
第七章 函數
7.1 將元數據信息附加到函數參數上
函數的參數注解可以提示程序員該函數應該如何使用,這是很有幫助的。比如,考慮下面這個帶參數注解的函數:
def add(x:int, y:int) -> int:
return x + y
python解釋器并不會附加任何語法意義到這些參數注解上。但參數注解會給閱讀代碼的人提供提示,并且一些第三方工具和框架可能會為注解加上語法含義。這些注解也會出現在文檔中:
help(add)
Help on function add in module __main__:
add(x:int, y:int) -> int
另外,函數注解還可以用來實現函數重載。
7.2 在匿名函數中綁定變量的值
我們利用lambda表達式定義了一個匿名函數,但是也希望可以在函數定義的時候完成對特定變量的綁定。也許我們想的是這樣的:
In [1]: x = 10
In [2]: a = lambda y : x + y
In [3]: x = 20
In [4]: b = lambda y : x + y
In [5]: a(10)
Out[5]: 30
In [6]: b(10)
Out[6]: 30
可以看到,和我們預想的結果有差距,這是因為lambda函數是在運行時才進行變量的綁定,而不是在定義時進行。因此,為了達成目標,我們需要在定義匿名函數的時候就進行變量綁定:
In [7]: x = 10
In [8]: a = lambda y , x=x: x + y
In [9]: b(10)
Out[9]: 20
In [10]: x = 20
In [11]: b = lambda y, x=x: x + y
In [12]: a(10)
Out[12]: 20
In [13]: b(10)
Out[13]: 30
7.3 讓帶有N個參數的可調用對象以較少的參數形式調用 functools.partial()
函數partial()允許我們給一個或多個參數指定固定的值,一次來減少參數的數量。
In [14]: def spam(a, b, c, d):
...: print(a, b, c, d)
In [15]: from functools import partial
In [17]: s1 = partial(spam, 1)
In [18]: s1(2, 3, 4)
(1, 2, 3, 4)
In [19]: s1(4, 5, 6)
(1, 4, 5, 6)
In [20]: s2 = partial(spam, d=42)
In [21]: s2(4, 5, 5)
(4, 5, 5, 42)
In [24]: s3 = partial(spam, 1, 2, d=42)
In [25]: s3(5)
(1, 2, 5, 42)
這個東西的主要用途是和那些只接受單一參數的函數來一起工作。如sort()函數:
In [26]: points = [ (1, 2), (3, 4), (5, 6), (7, 8) ]
In [27]: import math
In [28]: def distance(p1, p2):
...: x1, y1 = p1
...: x2, y2 = p2
...: return math.hypot(x2 - x1, y2 - y1)
In [29]: pt = (4, 3)
In [30]: points.sort(key=partial(distance, pt))
In [31]: points
Out[31]: [(3, 4), (1, 2), (5, 6), (7, 8)]
更一般來講,partial() 常常可以用來調整其他庫中用到的回調函數的參數簽名。
7.4 在回調函數中攜帶額外的狀態
一種在回調函數中攜帶額外信息的方法是使用綁定方法而不是普通的函數,比如下面這個類保存了一個內部的序列號碼,每當接收到一個結果時就遞增這個號碼:
In [32]: class ResultHandler:
...: def __init__(self):
...: self.sequence = 0
...: def handler(self, result):
...: self.sequence += 1
...: print('[{}] Got : {}'.format(self.sequence, result))
In [33]:apply_async(add, (2, 3), callback=r.handler)
Got: 5
In [33]:apply_async(add, ('hello', 'world'), callback=r.handler)
Got: helloworld
作為替代方案,也可以使用閉包來捕獲狀態:
def make_handler():
sequence = 0
def handler(result):
nonlocal sequence
sequence += 1
print('[{}] Got : {}'.format(self.sequence, result))
return handler
除此之外還可以使用協程(coroutine)來完成同樣的任務:
def make_handler():
sequence = 0
while True:
result = yield
sequence += 1
print('[{}] Got : {}'.format(self.sequence, result))
對于協程來說,可以使用它的send()函數作為回調函數:
handler = make_handler()
next(handler)
apply_async(add, (2, 3), callback=handler.send)
[1] Got: 5
這里對協程做一個筆記,使用協程的程序調用時不是棧的關系,在子程序內部可以中斷,然后轉而執行別的子程序,在適當的時候再返回來接著執行。類似于CPU終端,而非函數調用。在python中,yield可在一定程度上實現協程。使用send 到另一個程序運行。
最后,同樣重要的是也可以通過額外的參數在回調函數中攜帶狀態,然后用partial()來處理參數個數的額問題。在現實問題張,閉包可能顯得更輕量級一些,而且由于閉包是函數構建的,這樣會顯得更自然。將協程作為回調函數的有趣之處在于這種方式和采用閉包的方案關系緊密,從某種意義上說,協程甚至更為清晰,不過較難理解。
7.5 內聯回調函數
我們正在編寫使用回調函數的額代碼,但是擔心小型函數在代碼中大肆泛濫,程序的控制流會因此而失控。這時我們可以通過生成器和協程講回調函數內聯到一個函數中。從而使得回調函數得到隱藏。
def apply_async(func, args, *, callback):
# Compute the Result
result = func(*args)
# Invoke the callback with result
callback(result)
from queue import Queue
from functools import wraps
class Async:
def __init(self, func, args):
self.func = func
self.args = args
def inlined_async(func):
@wraps(func)
def wrapper(*args):
f = func(*args)
result_queue = Queue()
result_queue.put(None)
while True:
result = result_queue.get()
try:
a = f.send(result)
apply_async(a.func, a.args, callback=result_queue.put)
except StopIteration:
break
return wrapper
# When used
def add(x, y):
return x + y
@inlined_async
def test():
r = yield Async(add, (2, 3))
print(r)
r = yield Async(add, ('hello', 'world'))
print(r)
for n in range(10):
r = yield Async(add, (n, n))
print(r)
print('Goodbye')
# Result
5
helloworld
0
2
4
6
8
10
12
14
16
18
Goodbye
可以看到,除了那個特殊的裝飾器和對yield的使用之外,我們會發現代碼中根本沒有出現回調函數(它被隱藏到幕后了)。將精巧的控制流隱藏在生成器函數之后,這種做法可以在標準庫及第三方包中找到。
7.6 訪問閉包中的變量
一般來說,在閉包內層定義的變臉對于外界來說是完全隔離的。但是可以通過編寫存取函數(getter/setter 方法)并將他們作為函數屬性附加到閉包上來提供對內層變量的訪問支持。如:
def sample():
n = 0
# Closure function
def func():
print('n=', n)
# Accessor methods for n
def get_n():
return n
def set_n():
nonlocal n
n = value
# Attach as function attr
func.get_n = get_n
func.set_n = set_n
return func
采用上述方法還可以讓閉包模擬成類實例,我們要做的就是將內層函數拷貝到一個實例字典中然后將它返回。通常來說,采用閉包的版本有可能更快一些,因為不用涉及額外的self變量。
第八章 類與對象
8.1 修改實例的字符串表示 repr() 和 str()
要修改實例的字符串表示,可以通過定義 str() 和 repr() 方法來實現。特殊方法 repr()返回的是實例的代碼表示。通常可以用它發揮的字符串文版本重新創建這個實例,即滿足 obj == eval(repr(obj))。如:
class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Pair ({0.x!r}, {0.y!r})'.format(self)
def __str__(self):
return '({0.x!s}, {0.y!s})'.format(self)
p = Pair(3, 4)
p
Pair(3, 4)
print(p)
(3, 4)
通常認為定義 repr() 和 str()是好的編程實踐,因為這么做可以簡化調試過程和實例的輸出。
8.2 自定義字符串的輸出格式 format()
目的是想通過format()函數和字符串方法來支持自定義的輸出格式。可以通過在類內定義 format()來實現,一個 format()的例子:
_formats = {
'ymd' : '{d.year} - {d.month} - {d.day}',
'mdy' : ...,
'dmy' : ...
}
class Date:
def __init__(self, year, month, day):
...
def __format__(self, code):
if code == '':
code = 'ymd'
fmt = _formats[code]
return fmt.format(d=self)
8.3 讓對象支持上下文管理協議 enter() 和 exit()
要讓對象能夠兼容with 語句,需要實現 enter() 和 exit() 方法。
from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address =address
self.family = AF_INET
self.type = SOCK_STREAM
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock # 當用 with conn as s 這種時,這個s就是返回值 self.sock
def __exit__(self, exc_ty, exc_cal, tb):
self.sock.close()
self.sock = None
8.4 將名稱封裝到類中
python 中不像c++ 有private 那種個東西,但是通常認為:任何以單下劃線開頭的名字應該總是被認為只屬于內部實現。
以雙下劃線開頭的名稱會導致出現命名重整的行為,如在類B中實現的private_method 則會被重命名為_Bprivate_method。這樣重整的目的在于以雙下劃線開頭的屬性不能通過繼承而覆蓋。
8.5 創建可管理的屬性 @property
要自定義對屬性的訪問,一種簡單的方式是將其定義為 property,即把類中定義的函數當做一種屬性來使用。下面的例子定義了一個 property,增加了對屬性的類型檢查:
class Person:
def __init__(self, first_name):
self._first_name = first_name
# Getter function
@property
def first_name(self):
return self._first_name
# Setter function
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError('Expected a string')
self._first_name = value
# Deleter function
@first_name.deleter
def first_name(self):
raise AttributeError("Can't delete attribute")
在上述代碼中,一共有三個互相關聯的方法,它們必須有著相同的名稱。第一個方法是getter 函數,并將first_name 定義為 property屬性,其他兩個可選方法附加到了first_name屬性上。 property的重要特性就是它看起來就像一個普通的屬性,但是根據訪問它的不同方式,會自動出發getter、setter、deleter 方法。
property也可以用來定義需要計算的屬性。這類屬性并不會實際保存起來,而是根需要計算完成。如:
import math
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return math.pi * self.radius ** 2
@property
def perimeter(self):
return 2 * math.pi * self.radius
c = Cirle(4.0)
c.radius
4
c.area
50.2654824
c.perimeter
25.132741228
需要注意的是,不要編寫那種定義了大量重復性 property 的代碼,這會導致代碼膨脹,容易出錯。
8.6 在子類中擴展屬性
在子類中擴展在父類中已經存在的屬性,首先需要弄清楚是需要重新定義所有的方法還是只針對其中一個方法做擴展。要重新定義所有的方法很容易,只要吧 getter 、setter、Deleter都實現一遍就好。但當只針對其中的一個方法做擴展時,只使用@property是不夠的,如下面代碼是無法工作的:
class SubPerson(Person):
@property
def name(self):
print('Getting name')
return super().name
當使用上述代碼時,會發現setter函數消失不見了相反,我們應該這么做:
class SubPerson(Person):
@Person.getter
def name(self):
print('Getting name')
return super().name
通過這種方式,之前定義的所有屬性方法都會被拷貝過來,而getter函數則會被替換掉。
8.7 描述符
所謂的描述符就是以特殊方法 get()、set()、delete() 的形式實現了三個核心的屬性訪問操作的類。這些方法通過接受類實例作為輸入來工作。之后,底層的實例字典 dict 會根據需要適當的進行調整。要使用一個描述符,我們把描述符的實例放置在類的定義中作為類變量來使用。
對于大多數Python 類的特性,描述符都提供了底層的魔法,包括@classmethod、@staticmethod、@property甚至 slots。通過定義一個描述符,我們可以在很底層的情況下捕獲關鍵的實例操作(get、set、delete)。關于描述符,長容易困惑的地方就是它們只能在類的層次上定義,不能根據實例來產生。
8.8 定義一個接口或抽象基類
抽象基類的核心特征就是不能被直接實例化,它是用來給其他的類當做基類使用的,主要用途是強制規定所需要的編程接口。要定義一個抽象基類,可以使用abc 模塊:
from abc import ADCMeta, abstractmethod
class IStream(metaclass=ABCmeta):
@abstractmethod
def read(self, maxbytes=-1):
pass
@abstractmethod
def write(self, data):
pass
同時,抽象基類也允許其他的類向其注冊,然后實現所需的接口:
import io
# REgister the built-in I/O classes as supporting our interface
IStream.register(io.IOBase)
# Open a normal file and type check
f = open('foo.txt')
isinstance(f, IStream)
此處內容較多,建議看書。
8.8 委托屬性的訪問
我們想在訪問實例的屬性時能夠將其委托到一個內部持有的對象上,這可以作為繼承的替代方案或者是為了實現一種代理機制。
簡單的說,委托是一種編程模式,我們將某個特定的操作轉交給(委托)另一個不同的對象實現。最簡單的委托看起來是這樣的:
class A:
def spam(self, x):
pass
def foo(self):
pass
class B:
def __init__(self):
self._a = A()
def spam(self, x):
# Delegate to the internal self._a instance
return self._a.spam(x)
def foo(self):
return self._a.foo()
def bar(self):
pass
當僅有幾個方法需要委托時,上面的代碼是非常簡單的。但當有許多方法被委托時,另一種實現法師是定義getattr()方法。
class A:
def spam(self, x):
pass
def foo(self):
pass
class B:
def __init__(self):
self._a = A()
def bar(self):
pass
# Expose all of the methods defined on class A
def __getter__(self, name):
return getter(self._a, name)
有時候當直接使用繼承可能沒多大意義,或者我們想要更多地控制對象之間的關系,或者說進一步封裝,如只暴露出特定的方法、實現接口等,此時使用委托會很有用。
當使用委托來實現代理是,需要注意的是,getattr()實際上是一個回滾(fallback)方法,它只會在某個屬性沒有找到的時候才會調用。
8.9 在類中定義多個構造函數
要定義一個含有多個構造函數的類,應該使用類方法。
import time
In [7]: class Date:
...: # Primary constructor
...: def __init__(self, year, month, day):
...: self.year = year
...: self.month = month
...: self.day = day
...: # Alternate constructor
...: @classmethod
...: def today(cls):
...: t = time.localtime()
...: return cls(t.tm_year, t.tm_mon, t.tm_mday)
In [8]: b = Date.today()
In [9]: a = Date(2012, 12, 32)
類方法的一個關鍵特性就是把類作為其接收的第一個參數(cls),類方法中會用到這個類來創建并返回最終的實例。
8.10 不通過調用 init 來創建實例 new()
可以直接調用 new()方法來創建一個未初始化的實例。如:
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# no init
d = Date.__new__(Date)
8.11 使用Mixin 技術來擴展類定義
所謂Mixin技術是一種開發模式。python 的Mixin 模式可以通過多繼承來實現,是在繼承了一個基類的基礎上,順帶利用多繼承的功能給子類加東西。 Mixin 在定義類的過程中改變類的繼承順序,繼承類。當某個模塊不能修改時,通過 mixin方式可以動態添加或修改類的原有繼承體系, 通常 Mixin類不能作為任何類的基類,在運行時要同其他類一起使用。
8.12 實現帶有狀態的對象或狀態機
當程序中出現大量的狀態分量,即包含大量的狀態,并對不同的狀態有不同的操作。如果你通過if-else 條件判斷則看起來很丑。此時我們可以通過將各個狀態分解為單獨的類來避免這個問題。看起來很奇怪,但其實產生這種設計的原因是我們決定在不同的狀態中不保存任何的實例數據。相反,所有的實例數據應該保存在 Connection 實例中,將所有的狀態放在一個公共基類下,可以幫助組織代碼。
8.13 實現訪問者模式
訪問者模式,是行為型設計模式之一。訪問者模式是一種將數據操作與數據結構分離的設計模式。在本書的介紹中主要包含了兩點核心思想。首先是涉及策略,即把操作復雜的數據結構的代碼和數據結構本身進行解耦,吧所有對數據的處理都放到特定的類中實現,這種隔離是的代碼變得十分通用。
另一方面,對于訪問者類的實現,我們可以通過一些小技巧將方法名構建出來,再利用 getattr()函數來獲取更多的方法。在每個訪問者類中,常常會對visit()方法進行遞歸調用來完成計算。
訪問者模式的一個缺點就是需要重度依賴于遞歸,而python 對遞歸層數有限制。因此需要做一定的處理,一種方法為用普通的python 來代替鏈表,或者在每個節點中聚合更多數據,使得數據變得扁平化而不是深度嵌套。當然用yield 就可以實現非遞歸的訪問者模式。
8.14 在換裝數據結構中管理內存 weakref
python 的垃圾回收器是基于簡單的引用計數規則實現的。當對象的引用技術為0時就會被立刻刪除掉。而對于環裝數據則絕不會發生,因此需要使用weakref。weakref 模塊采用的是弱引用機制,本質上說,弱引用就是一個指向對象的指針,但不會增加對象本身的引用計數。 可以通過 weakref.ref()來創建。
8.15 讓類支持比較操作 functools.total_ordering
要想讓類支持比較操作,一種方法是將 eq()、 lt()、le()、gt()、ge()都實現一遍,否則少哪個就不支持哪個,這很麻煩通過使用 functools.total_ordering模塊,只需要你定義一個 eq()和 (lt、le、gt、ge)中的一個,類裝飾器就會為我們實現其他的比較方法。
8.16 工廠函數
工廠函數是指這些內建函數都是類對象,當你調用它們時,實際上是創建了一個類實例。工廠函數根據不同的參數,生成不同的實例。
總結
以上是生活随笔為你收集整理的python cookbook 自营_Python Cookbook总结 7-8 章的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pythondatetime小时_使用p
- 下一篇: python flask跨域_Flask