8.1 修改實例的字符串表示
format和%,format函數的0,實際上表示的是self, 0.x用來指代0的x屬性。
>>> print('p is {0}'.format((3,4)))
p is (3, 4)
>>> print('p is {}'.format((3,4)))
p is (3, 4)
>>> print("today is %s年%s月%s日" % ('2020', '05', '22'))
today is 2020年05月22日
8.2 讓對象支持上下文管理協議
要讓對象能夠兼容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
def __exit__(self, exc_type, exc_val, exc_tb):
self.sock.close()
self.sock = None
from functools import partial
conn = LazyConnection(('www.python.org', 80))
with conn as s:
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.python.org\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
print("resp = ", resp)
改造上面的示例,嵌套with創建多個socket。把LazyConnection做成一個專門生產網絡連接的工廠類,在內部實現中,把一個列表當成棧使用來保存連接。每當__enter__()執行時,由它產生一個新的連接並添加到棧中。而__exit__()方法只是簡單的將最近加入的那個連接從棧中彈出並關閉它。這個修改可以允許嵌套式的with語句一次創建多個連接了。
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.connections = []
def __enter__(self):
sock = socket(self.family, self.type)
sock.connect(self.address)
sock.connections.append(sock)
return sock
def __exit__(self, exc_type, exc_val, exc_tb):
self.connections.pop().close()
from functools import partial
conn = LazyConnection(('www.python.org', 80))
with conn as s1:
...
with conn as s2:
...
上下文管理器最常用在需要管理類似文件,網絡連接和鎖這樣的資源程序中。例如:如果獲得了一個鎖,之後就必須釋放它,否則就會有死鎖的風險。通過實現__enter__()和__exit__(),並且利用with語句來觸發,這類問題就可以很容易的避免了,因爲__exit__()方法中的清理代碼,無論如何都會保證運行的。
8.3 調用父類中的方法:
父類(或稱超類)中的方法被子類覆蓋掉,可以使用super()函數來完成。
>>> class A:
... def spam(self):
... print ("A spam")
...
>>> class B(A):
... def spam(self):
... print ("B spam")
... super().spam()
...
>>> B().spam()
B spam
A spam
>>>
8.4 大量if-elif-else塊代碼,改造成另一種代替方式
class State:
def __init__(self):
self.state = 'A'
def action(self, state=''):
if self.state == 'A':
print("----action A")
self.state = "B"
elif self.state == 'B':
print("----action B")
self.state = "C"
elif self.state == 'C':
print("----action C")
self.state = "A"
else:
print("----action no")
self.state = "no"
ts = State()
for i in range(0,4):
ts.action()
運行結果:
----action A
----action B
----action C
----action A
這種大量的判斷,可以改成以下方式實現:
class StateBase():
def __init__(self):
self.new_state(State_A)
def new_state(self, state=""):
self.__class__ = state
def action(self, x=''):
raise NotImplementedError()
class State_A(StateBase):
def action(self, x=''):
print("State_A--")
self.new_state(State_B)
class State_B(StateBase):
def action(self, x=''):
print("State_B--")
self.new_state(State_C)
class State_C(StateBase):
def action(self, x=''):
print("State_C--")
self.new_state(State_A)
tsC = State_C()
for i in range(0,4):
tsC.action()
運行結果:
State_A--
State_B--
State_C--
State_A--
8.5 實現訪問者模式
#訪問者類
class NodeVisitor:
def visit(self, node):
methname = 'visit_' + type(node).__name__
print("methname = ", methname)
meth = getattr(self, methname, None)
print("meth = ", meth)
if meth is None:
meth = self.generic_visit
return meth(node) #改變元素類的執行算法,元素的執行算法隨着訪問者改變而改變
def generic_visit(self, node):
raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))
class Evaluator(NodeVisitor):
def visit_Number(self, node):
return node
def visit_float(self, node):
return node
def visit_int(self, node):
print("node = ", node)
return node
if __name__ == '__main__':
e = Evaluator()
e.visit(4)
運行結果:
methname = visit_int
meth = <bound method Evaluator.visit_int of <__main__.Evaluator object at 0x00000000034AD6D8>>
4
8.6 在環狀數據結構中管理內存
常見的環狀數據結構:樹、圖、觀察者模式
(1)例如:樹,父節點指向它的孩子,而孩子節點又會指回他們的父節點。方式1:考慮讓其中一條連接使用weakref庫中提供的弱引用機制。示例:
import weakref
class Node:
def __init__(self, value):
self.value = value
self._parent = None
self.children = []
def __repr__(self):
return 'Node({!r:})'.format(self.value)
@property
def parent(self):
return self._parent if self._parent is None else self._parent()
@parent.setter
def parent(self, node):
self._parent = weakref.ref(node)
def add_child(self, child):
self.children.append(child)
child.parent = self
#這種實現可以讓父節點安靜的被回收,示例:
root = Node('parent')
cl = Node('child')
print("cl = ", cl)
root.add_child(cl)
print("cl.parent 1 = ", cl.parent)
del root
print("cl.parent 2 = ", cl.parent)
#運行結果輸出:
cl = Node('child')
cl.parent 1 = Node('parent')
cl.parent 2 = None
(2)做一個試驗,以下示例可以看到:除了最後那種涉及成環的情況,其他對象都,可以立刻得到刪除。原因在於python的垃圾收集器是基於簡單的引用計數原則來實現的。當對象的引用計數爲0時就會被立刻刪除掉。而對於環狀數據結構來說這絕對不可能發生。因爲在最後那種情況中,由於父節點和子節點相互引用對方,引用計數不會爲0,。
class Data:
def __del__(self):
print("Data.__del__")
class Node:
def __init__(self):
self.data = Data()
self._parent = None
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = self
a = Data()
del a #運行輸出: Data.__del__
a = Node()
del a #運行輸出: Data.__del__
a = Node()
a.add_child(Node())
del a #運行未輸出,沒有打印任何東西
要處理環狀數據結構,還有一個單獨的垃圾收集器會定期運行。但是一般來說,我們不知道它會在何時運行,因此,沒法知道環狀數據結構具體會在何時被回收。如果有必要的話,可以強制運行垃圾收集器,但是這相比於全自動的垃圾收集會有一些笨拙。
(3)要提領(derefrenbce)一個弱引用,可以像函數一樣來調用它。如果提領後得到的對象還依然存在,那麼就返回對象,否則就返回None。由於原始對象的引用計數並沒有增加,因此可以按照普通的方式刪除它。通過使用弱引用,就會發現因爲循環而出現的問題都不存在了。一旦某個對象不再被使用了,會立刻執行垃圾收集處理。