Python 如何实现访问者模式


Posted in Python onJuly 28, 2020

问题

你要处理由大量不同类型的对象组成的复杂数据结构,每一个对象都需要需要进行不同的处理。比如,遍历一个树形结构,然后根据每个节点的相应状态执行不同的操作。

解决方案

这里遇到的问题在编程领域中是很普遍的,有时候会构建一个由大量不同对象组成的数据结构。假设你要写一个表示数学表达式的程序,那么你可能需要定义如下的类:

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

然后利用这些类构建嵌套数据结构,如下所示:

# Representation of 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__))

为了使用这个类,可以定义一个类继承它并且实现各种 visit_Name() 方法,其中Name是node类型。例如,如果你想求表达式的值,可以这样写:

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',)]
>>>

讨论

刚开始的时候你可能会写大量的if/else语句来实现,这里访问者模式的好处就是通过 getattr() 来获取相应的方法,并利用递归来遍历所有的节点:

def binop(self, node, instruction):
  self.visit(node.left)
  self.visit(node.right)
  self.instructions.append((instruction,))

还有一点需要指出的是,这种技术也是实现其他语言中switch或case语句的方式。比如,如果你正在写一个HTTP框架,你可能会写这样一个请求分发的控制器:

class HTTPHandler:
  def handle(self, request):
    methname = 'do_' + request.request_method
    getattr(self, methname)(request)
  def do_GET(self, request):
    pass
  def do_POST(self, request):
    pass
  def do_HEAD(self, request):
    pass

访问者模式一个缺点就是它严重依赖递归,如果数据结构嵌套层次太深可能会有问题,有时候会超过Python的递归深度限制(参考 sys.getrecursionlimit() )。

在跟解析和编译相关的编程中使用访问者模式是非常常见的。Python本身的 ast 模块值的关注下,可以去看看源码。

以上就是Python 如何实现访问者模式的详细内容,更多关于Python 访问者模式的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python socket.error: [Errno 98] Address already in use的原因和解决方法
Aug 25 Python
关于Python面向对象编程的知识点总结
Feb 14 Python
浅谈python之新式类
Aug 12 Python
对python中数据集划分函数StratifiedShuffleSplit的使用详解
Dec 11 Python
python利用ffmpeg进行录制屏幕的方法
Jan 10 Python
python版DDOS攻击脚本
Jun 12 Python
用python打印1~20的整数实例讲解
Jul 01 Python
django 单表操作实例详解
Jul 30 Python
Python图像处理库PIL的ImageDraw模块介绍详解
Feb 26 Python
深入了解Python 方法之类方法 & 静态方法
Aug 17 Python
python3实现飞机大战
Nov 29 Python
Django数据统计功能count()的使用
Nov 30 Python
Matplotlib 折线图plot()所有用法详解
Jul 28 #Python
matplotlib.pyplot.plot()参数使用详解
Jul 28 #Python
matplotlib图例legend语法及设置的方法
Jul 28 #Python
Matplotlib中%matplotlib inline如何使用
Jul 28 #Python
Python基于xlrd模块处理合并单元格
Jul 28 #Python
Python 在函数上添加包装器
Jul 28 #Python
Python matplotlib图例放在外侧保存时显示不完整问题解决
Jul 28 #Python
You might like
php strstr查找字符串中是否包含某些字符的查找函数
2010/06/03 PHP
PHP查询快递信息的方法
2015/03/07 PHP
php计算年龄精准到年月日
2015/11/17 PHP
asp.net和php的区别点总结
2019/10/10 PHP
Js动态创建div
2008/09/25 Javascript
javascript中的toFixed固定小数位数 简单实例分享
2013/07/12 Javascript
jquery 绑定回车动作扑捉回车键触发的事件
2014/03/26 Javascript
Json实现异步请求提交评论无需跳转其他页面
2014/10/11 Javascript
javascript实现状态栏文字首尾相接循环滚动的方法
2015/07/22 Javascript
jQuery EasyUI datagrid在翻页以后仍能记录被选中行的实现代码
2016/08/15 Javascript
教大家轻松制作Bootstrap漂亮表格(table)
2016/12/13 Javascript
详解JavaScript 中getElementsByName在IE中的注意事项
2017/02/21 Javascript
vue父组件向子组件传递多个数据的实例
2018/03/01 Javascript
angular4自定义表单控件[(ngModel)]的实现
2018/11/23 Javascript
JSON的parse()方法介绍
2019/01/31 Javascript
详解es6新增数组方法简便了哪些操作
2019/05/09 Javascript
js核心基础之闭包的应用实例分析
2019/05/11 Javascript
Node爬取大批量文件的方法示例
2019/06/28 Javascript
Vue自定义render统一项目组弹框功能
2020/06/07 Javascript
解决vue+elementui项目打包后样式变化问题
2020/08/03 Javascript
如何构建 vue-ssr 项目的方法步骤
2020/08/04 Javascript
python操作摄像头截图实现远程监控的例子
2014/03/25 Python
分享15个最受欢迎的Python开源框架
2014/07/13 Python
Python基于Tkinter的HelloWorld入门实例
2015/06/17 Python
批量获取及验证HTTP代理的Python脚本
2017/04/23 Python
python字符串常用方法
2018/06/14 Python
wxPython色环电阻计算器
2019/11/18 Python
Python 实现PS滤镜中的径向模糊特效
2020/12/03 Python
美国女性奢华品牌精品店:INTERMIX
2017/10/12 全球购物
什么是Assembly(程序集)
2014/09/14 面试题
可贵的沉默教学反思
2014/02/06 职场文书
做一个有道德的人演讲稿
2014/05/14 职场文书
国际贸易毕业生求职信
2014/07/20 职场文书
服装区域经理岗位职责
2015/04/10 职场文书
2016年党员承诺书范文
2016/03/24 职场文书
使用Bandicam录制鼠标指针并附带点击声音,还可以添加点击动画效果
2022/04/11 数码科技