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 22 Python
Python执行时间的计算方法小结
Mar 17 Python
python递归打印某个目录的内容(实例讲解)
Aug 30 Python
Python人脸识别初探
Dec 21 Python
基于循环神经网络(RNN)的古诗生成器
Mar 26 Python
Selenium定时刷新网页的实现代码
Oct 31 Python
使用Pandas对数据进行筛选和排序的实现
Jul 29 Python
python matplotlib库绘制散点图例题解析
Aug 10 Python
python实现H2O中的随机森林算法介绍及其项目实战
Aug 29 Python
python实现大学人员管理系统
Oct 25 Python
python将图片转为矢量图的方法步骤
Mar 30 Python
Python趣味挑战之实现简易版音乐播放器
May 28 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循环获取GET和POST值的代码
2008/04/09 PHP
解析PHP自带的进位制之间的转换函数
2013/06/08 PHP
laravel-admin的图片删除实例
2019/09/30 PHP
php封装的page分页类完整实例代码
2020/02/01 PHP
PHP随机生成中文段落示例【测试网站内容时使用】
2020/04/26 PHP
Ext.FormPanel 提交和 Ext.Ajax.request 异步提交函数的区别
2009/11/12 Javascript
JavaScript 高级篇之闭包、模拟类,继承(五)
2012/04/07 Javascript
JavaScript验证图片类型(扩展名)的函数分享
2014/05/05 Javascript
关于Javascript加载执行优化的研究报告
2014/12/16 Javascript
浅谈bootstrap源码分析之scrollspy(滚动侦听)
2016/06/06 Javascript
详解微信小程序 wx.uploadFile 的编码坑
2017/01/23 Javascript
Extjs 中的 Treepanel 实现菜单级联选中效果及实例代码
2017/08/22 Javascript
微信禁止下拉查看URL的处理方法
2017/09/28 Javascript
vue项目引入字体.ttf的方法
2018/09/28 Javascript
一百行JS代码实现一个校验工具
2019/04/30 Javascript
微信小程序合法域名配置方法
2019/05/06 Javascript
Vue之Mixins(混入)的使用方法
2019/09/24 Javascript
原生js+css调节音量滑块
2020/01/15 Javascript
关于你不想知道的所有Python3 unicode特性
2014/11/28 Python
Python3操作SQL Server数据库(实例讲解)
2017/10/21 Python
浅谈python numpy中nonzero()的用法
2018/04/02 Python
python如何创建TCP服务端和客户端
2018/08/26 Python
详解用selenium来下载小姐姐图片并保存
2021/01/26 Python
html+css实现自定义图片上传按钮功能
2019/09/04 HTML / CSS
关于canvas.toDataURL 在iOS运行失败的问题解决
2020/09/16 HTML / CSS
世界顶级户外运动品牌折扣网站:LeftLane Sports
2019/06/12 全球购物
Etam德国:内衣精品店
2019/08/25 全球购物
乌克兰机票、铁路和巴士票、酒店搜索、保险:Tickets.ua
2020/01/11 全球购物
Berghaus官网:户外服装和设备,防水服
2020/01/17 全球购物
英国发展最快的在线超市之一:Click Marketplace
2021/02/15 全球购物
党员廉洁自律承诺书
2014/05/26 职场文书
司法工作人员群众路线对照检查材料思想汇报
2014/09/30 职场文书
2014年医院个人工作总结
2014/12/09 职场文书
2016年大学生社会实践心得体会
2015/10/09 职场文书
比较node.js和Deno
2021/04/27 Javascript
OpenCV-Python实现轮廓拟合
2021/06/08 Python