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获取运行目录与当前脚本目录的方法
Jun 01 Python
一个基于flask的web应用诞生 使用模板引擎和表单插件(2)
Apr 11 Python
一文总结学习Python的14张思维导图
Oct 17 Python
python实现用户答题功能
Jan 17 Python
TensorFlow深度学习之卷积神经网络CNN
Mar 09 Python
python smtplib发送带附件邮件小程序
May 22 Python
Django 登陆验证码和中间件的实现
Aug 17 Python
更改Python的pip install 默认安装依赖路径方法详解
Oct 27 Python
OpenCV3.0+Python3.6实现特定颜色的物体追踪
Jul 23 Python
详解python变量与数据类型
Aug 25 Python
Pycharm 如何一键加引号的方法步骤
Feb 05 Python
教你怎么用python实现字符串转日期
May 24 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之源码目录结构与功能说明
2016/06/01 PHP
laravel 根据不同组织加载不同视图的实现
2019/10/14 PHP
PHP7 其他修改
2021/03/09 PHP
Javascript 文件夹选择框的两种解决方案
2009/07/01 Javascript
基于jquery tab切换(防止页面刷新)
2012/05/23 Javascript
我用的一些Node.js开发工具、开发包、框架等总结
2014/09/25 Javascript
JavaScript的9种继承实现方式归纳
2015/05/18 Javascript
如何动态加载外部Javascript文件
2015/12/02 Javascript
javascript关于继承解析
2016/05/10 Javascript
聊一聊Vue.js过渡效果
2016/09/07 Javascript
Javascript计算二维数组重复值示例代码
2016/12/18 Javascript
浅谈如何使用 webpack 优化资源
2017/10/20 Javascript
微信小程序实现animation动画
2018/01/26 Javascript
js保留两位小数方法总结
2018/01/31 Javascript
Vue 组件封装 并使用 NPM 发布的教程
2018/09/30 Javascript
vue项目首屏加载时间优化实战
2019/04/23 Javascript
Layui Form 自定义验证的实例代码
2019/09/14 Javascript
js消除图片小游戏代码
2019/12/11 Javascript
Vue-cli3生成的Vue项目加载Mxgraph方法示例
2020/05/31 Javascript
Javascript中Math.max和Math.max.apply的区别和用法详解
2020/08/24 Javascript
Ant Design Vue table中列超长显示...并加提示语的实例
2020/10/31 Javascript
仅利用30行Python代码来展示X算法
2015/04/01 Python
Python实现基于C/S架构的聊天室功能详解
2018/07/07 Python
pygame游戏之旅 添加游戏界面按键图形
2018/11/20 Python
基于腾讯云服务器部署微信小程序后台服务(Python+Django)
2019/05/08 Python
python实现坦克大战游戏 附详细注释
2020/03/27 Python
Django CBV类的用法详解
2019/07/26 Python
Spartoo西班牙官网:法国时尚购物网站
2018/03/27 全球购物
六道php面试题附答案
2014/06/05 面试题
户籍证明的格式
2014/01/13 职场文书
小学美术教学反思
2014/02/01 职场文书
亮化工程实施方案
2014/03/17 职场文书
毕业评语大全
2014/05/04 职场文书
第一节英语课开场白
2015/06/01 职场文书
贷款工作证明模板
2015/06/12 职场文书
Python time库的时间时钟处理
2021/05/02 Python