python cookbook閱讀之——8. 類與對象

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。由於原始對象的引用計數並沒有增加,因此可以按照普通的方式刪除它。通過使用弱引用,就會發現因爲循環而出現的問題都不存在了。一旦某個對象不再被使用了,會立刻執行垃圾收集處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章