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。由于原始对象的引用计数并没有增加,因此可以按照普通的方式删除它。通过使用弱引用,就会发现因为循环而出现的问题都不存在了。一旦某个对象不再被使用了,会立刻执行垃圾收集处理。

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