Jupyter notebook模式查看效果,不一样的感觉,哈哈
8.21实现访问者模式
- 问题:需要处理大量不同类型的对象组成的复杂数据结构,每个对象需要进行不同的处理。比如遍历一个树形结构然后根据每个节点的状态执行不同的操作
- 方案:见下
class Node:
pass
class UnaryOperator(Node):
def __init__(self,operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self,left,right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self,value):
self.value = value
# 1+2*(3-4)/5
t1 = Sub(Number(3),Number(4))
t2 = Mul(Number(2),t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1),t3)
- 上述的做法太麻烦,这里使用访问者模式:
class NodeVisitor:
def visit(self,node):
methname = 'visit_' + type(node).__name__
meth = getattr(self, methname, None)
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.value
def visit_Add(self,node):
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self,node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self,node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self,node):
return self.visit(node.left) / self.visit(node.right)
def visit_Negate(self,node):
return -node.operand
'''使用'''
e = Evaluator()
e.visit(t4)
0.6
- 作为一个不同的例子,还可以在一个栈上面将一个表达式转换成多个操作序列
class StackCode(NodeVisitor):
def generate_code(self,node):
self.instructions = []
self.visit(node)
return self.instructions
def visit_Number(self,node):
self.instructions.append(('PUSH',node.value))
def binop(self,node,instruction):
self.visit(node.left)
self.visit(node.right)
self.instructions.append((instruction,))
def visit_Add(self,node):
self.binop(node,"ADD")
def visit_Sub(self,node):
self.binop(node,"SUB")
def visit_Mul(self,node):
self.binop(node,"MUL")
def visit_Div(self,node):
self.binop(node,"DIV")
def unaryop(self,node,instruction):
self.visit(node.operand)
self.instructions.append((instruction,))
def visit_Negate(self,node):
self.unaryop(node,"NEG")
s = StackCode()
s.generate_code(t4)
[('PUSH', 1),
('PUSH', 2),
('PUSH', 3),
('PUSH', 4),
('SUB',),
('MUL',),
('PUSH', 5),
('DIV',),
('ADD',)]
- 访问者模式的缺点是它严重的依赖递归,如果数据结构嵌套的很深可能会有问题
不用递归实现访问者模式
- 问题:不想用递归,但想继续使用访问者模式
- 方案:使用生成器
import types
class Node:
pass
class NodeVisitor:
def visit(self,node):
stack = [node]
last_result = None
while stack:
try:
last = stack[-1]
if isinstance(last, types.GeneratorType):
stack.append(last.send(last_result))
last_result = None
elif isinstance(last,Node):
stack.append(self._visit(stack.pop()))
else:
last_result = stack.pop()
except StopAsyncIteration:
stack.pop()
return last_result
def _visit(self,node):
methname = 'visit_' + type(node).__name__
meth = getattr(self,methname,None)
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 UnaryOperator(Node):
def __init__(self,operand):
self.operand = operand
class BinaryOperator(Node):
def __init__(self,left,right):
self.left = left
self.right = right
class Add(BinaryOperator):
pass
class Sub(BinaryOperator):
pass
class Mul(BinaryOperator):
pass
class Div(BinaryOperator):
pass
class Negate(UnaryOperator):
pass
class Number(Node):
def __init__(self,value):
self.value = value
class Evaluator(NodeVisitor):
def visit_Number(self,node):
return node.value
def visit_Add(self, node):
return self.visit(node.left) + self.visit(node.right)
def visit_Sub(self,node):
return self.visit(node.left) - self.visit(node.right)
def visit_Mul(self,node):
return self.visit(node.left) * self.visit(node.right)
def visit_Div(self,node):
return self.visit(node.left) / self.visit(node.right)
def visit_Negate(self,node):
return -self.visit(node.operand)
if __name__ == "__main__":
# 1+2*(3-4)/5
t1 = Sub(Number(3),Number(4))
t2 = Mul(Number(2),t1)
t3 = Div(t2, Number(5))
t4 = Add(Number(1),t3)
e = Evaluator()
print(e.visit(t4))
0.6
'''如果嵌套的太深,则上述的Evaluator会失效'''
a = Number(0)
for n in range(1,10000):
a = Add(a,Number(n))
e = Evaluator()
e.visit(a)
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-38-d04f2f9da915> in <module>()
4 a = Add(a,Number(n))
5 e = Evaluator()
----> 6 e.visit(a)
<ipython-input-30-893d1c94caf5> in visit(self, node)
13 last_result = None
14 elif isinstance(last,Node):
---> 15 stack.append(self._visit(stack.pop()))
16 else:
17 last_result = stack.pop()
<ipython-input-30-893d1c94caf5> in _visit(self, node)
25 if meth is None:
26 meth = self.generic_visit
---> 27 return meth(node)
28 def generic_visit(self,node):
29 raise RuntimeError('No {} method'.format('visit_'+type(node).__name__))
<ipython-input-37-fee856babcc6> in visit_Add(self, node)
23 return node.value
24 def visit_Add(self, node):
---> 25 return self.visit(node.left) + self.visit(node.right)
26 def visit_Sub(self,node):
27 return self.visit(node.left) - self.visit(node.right)
... last 3 frames repeated, from the frame below ...
<ipython-input-30-893d1c94caf5> in visit(self, node)
13 last_result = None
14 elif isinstance(last,Node):
---> 15 stack.append(self._visit(stack.pop()))
16 else:
17 last_result = stack.pop()
RecursionError: maximum recursion depth exceeded while calling a Python object
class Evaluator(NodeVisitor):
def visit_Number(self,node):
return node.value
def visit_Add(self, node):
yield (yield node.left) + (yield node.right)
def visit_Sub(self,node):
yield (yield node.left) - (yield node.right)
def visit_Mul(self,node):
yield (yield node.left) * (yield node.right)
def visit_Div(self,node):
yield (yield node.left) / (yield node.right)
def visit_Negate(self,node):
yield -(yield node.operand)
a = Number(0)
for n in range(1,100):
a = Add(a,Number(n))
e = Evaluator()
print(e.visit(a))
###有错误,但是没有找到原因
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-41-ccfeee670c57> in <module>()
3 a = Add(a,Number(n))
4 e = Evaluator()
----> 5 print(e.visit(a))
<ipython-input-30-893d1c94caf5> in visit(self, node)
10 last = stack[-1]
11 if isinstance(last, types.GeneratorType):
---> 12 stack.append(last.send(last_result))
13 last_result = None
14 elif isinstance(last,Node):
StopIteration:
8.23循环引用数据结构的内存管理
- 问题:你的程序创建了很多循环引用数据结构,碰到了内存管理难题
- 方案:使用弱引用
'''一个简单的循环引用数据结构的例子就是一个树形结构双亲节点有指针指向子节点,子节点又反过来指向双亲节点。
这时候可以使用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 None 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')
c1 = Node('child')
root.add_child(c1)
print(c1.parent)
Node('parent')
del root
print(c1.parent)
None
- 循环引用的数据结构时python中一个棘手的问题,因为正常的垃圾回收机制不适用:
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 #不会立即删除
- 最后一个删除语句并没有打印输出语句。因为python的垃圾回收机制是基于计数的,当一个对象的引用数变成0 的时候才会被立即删除。但对于循环引用永远不会成立,父节点和子节点互相引用,引用数永远不会为0 .
- python有另外的垃圾回收机制来专门针对循环引用但是代码看上去很挫
import gc
gc.collect()
Data.__del__
Data.__del__
Data.__del__
Data.__del__
699
'''如果循环引用还自己定义了__del__(),那么会变得更糟'''
'''弱引用消除了引用循环的问题,本质上说,弱引用就是一个对象指针,他不会增加它的引用计数'''
import weakref
a = Node()
a_ref = weakref.ref(a)
a_ref
<weakref at 0x05C1B240; to 'Node' at 0x067A1510>
'''为了访问所引用所引用的对象,可以想使用函数那样调用即可'''
print(a_ref())
<__main__.Node object at 0x067A1510>
del a
Data.__del__
print(a_ref())
None
8.24让类支持比较操作
- 问题:想让某个类支持比较操作,但用不想实现那么多特殊的方法
- 方案:使用functools.total_ordering
from functools import total_ordering
class Room:
def __init__(self,name,length,width):
self.name = name
self.length = length
self.width = width
self.square_feet = self.width * self.length
@total_ordering
class House:
def __init__(self,name,style):
self.name = name
self.rooms = list()
self.style = style
@property
def living_sapce_footage(self):
return sum(r.square_feet for r in self.rooms)
def add_room(self,room):
self.rooms.append(room)
def __str__(self):
return '{}: {} square foot {}'.format(self.name,self.living_sapce_footage,self.style)
def __eq__(self,other):
return self.living_sapce_footage == other.living_sapce_footage
def __lt__(self,other):
return self.living_sapce_footage < other.living_sapce_footage
h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h1.add_room(Room('Office', 12, 12))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
h2.add_room(Room('Living Room', 18, 20))
h2.add_room(Room('Kitchen', 12, 16))
h3 = House('h3', 'Split')
h3.add_room(Room('Master Bedroom', 14, 21))
h3.add_room(Room('Living Room', 18, 20))
h3.add_room(Room('Office', 12, 16))
h3.add_room(Room('Kitchen', 15, 17))
houses = [h1, h2, h3]
print('h1 bigger than h2?==> ',h1 > h2)
print('h1 smaller than h3?==> ',h1 < h3)
print('h2 greater than h2?==> ',h2 >= h1)
print('which is the biggest?==>',max(houses))
h1 bigger than h2?==> True
h1 smaller than h3?==> True
h2 greater than h2?==> False
which is the biggest?==> h3: 1101 square foot Split
- total_ordering装饰器其实就是定义了一个从每个比较方法到所需要定义的其它方法的一个映射而已。就像下面那样,只不过由@total_ordering简化了
class House:
def __eq__(self,house):
pass
def __lt__(self,other):
pass
#method created by @total_ordering
__le__ = lambda self,other:self < other or self == other
__gt__ = lambda self,other:not (self < other or self == other)
__ge__ = lambda self,other:not(self < other)
__ne__ = lambda self,other:not self < self == other
8.25创建缓存实例
- 问题:在创建一个类的对象时,如果之前使用同样的参数创建过这个对象,想直接返回它的缓存引用
- 方案:这种通常是因为你希望相同参数创建的对象时单例的。在很多库中都有实际的例
子,比如 logging 模块,使用相同的名称创建的 logger 实例永远只有一个。例如:
import logging
a = logging.getLogger('foo')
b = logging.getLogger('bar')
a is b
False
c = logging.getLogger('foo')
a is c
True
- 为了达到这样的效果,需要使用一个和类本身分开的工厂函数
class Spam:
def __init__(self,name):
self.name = name
import weakref
_spam_cache = weakref.WeakValueDictionary()
def get_spam(name):
if name not in _spam_cache:
s = Spam(name)
_spam_cache[name] = s
else:
s = _spam_cache[name]
return s
a = get_spam('foo')
b = get_spam('bar')
c = get_spam('foo')
a == b
False
a == c
True
- 此外还可以重新定义__new__()
import weakref
class Spam:
_spam_cache = weakref.WeakValueDictionary()
def __new__(cls,name):
if name in cls._spam_cache:
return cls._spam_cache[name]
else:
self = super().__new__(cls)
cls._spam_cache[name] = self
return self
def __init__(self,name):
print("initializing Spam...")
self.name = name
'''上述方法看起来可以达到预期的效果,但是问题是__init__()每次都会被调用,不管这个实例是否被缓存了'''
s = Spam('Dave')
initializing Spam...
t = Spam('Dave')
initializing Spam...
s is t
True
'''一个 WeakValueDictionary 实例只会保存那些在其它地方还在被使用的实例。
否则的话,只要实例不再被使用了,它就从字典中被移除了'''
a = get_spam('foo')
b = get_spam("bar")
c = get_spam('foo')
list(_spam_cache)
initializing Spam...
['foo', 'bar']
del a
del c
list(_spam_cache)
['foo', 'bar']
del b
list(_spam_cache)
['foo']
- 还可以有更加高级的实现方式,将缓存代码放到一个单独的缓存管理器中
import weakref
class CacheSpamManeger:
def __init__(self):
self._cache = weakref.WeakValueDictionary()
def get_spam(self,name):
if name not in self._cache:
s = Spam(name)
self._cache = s
else:
s = self._cache[name]
return s
def clear(self):
self._cache.clear()
class Spam:
manager = CacheSpamManeger()
def __init__(self,name):
self.name = name
def get_spam(name):
return Spam.manager.get_spam(name)
'''上述代码更加简洁,但是它暴露了类的实例化给用户,用户很容易直接实例化这个类'''
a = Spam('foo')
b = Spam('foo')
a == b
False
- 有几种方式可以防止用户这样做,一种是将类的名字以下划线开头,提示用户别直接调用它。另一种是让这个类的init函数抛出异常
class Spam:
def __init__(self, * args, **kwargs):
raise RuntimeError("can't instantiate directly")
@classmethod
def _new(cls,name):
self = cls.__new__(cls)
self.name = name
'''然后修改缓存管理器的代码,使用Spam.__new__()来创建实例,而不是直接调用Spam()构造函数'''
class CachedSpamManager2:
def __init__(self):
self._cache = weakref.WeakValueDictionary()
def get_spam(self,name):
if name not in self._cache:
temp = Spam3._new(name)
self._cache[name] = temp
else:
temp = self._cache[name]
return temp
def clear(self):
self._cache.clear()
class Spam3:
def __init__(self, *args, **kwargs):
raise RuntimeError("can't instantiate directly")
@classmethod
def _new(cls,name):
self = cls.__new__(cls)
self.name = name
return self