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登录QQ邮箱发信的实现代码
Feb 10 Python
Python显示进度条的方法
Sep 20 Python
Python中的深拷贝和浅拷贝详解
Jun 03 Python
Python fileinput模块使用实例
Jun 03 Python
python使用mysql的两种使用方式
Mar 07 Python
TensorFlow平台下Python实现神经网络
Mar 10 Python
python中break、continue 、exit() 、pass终止循环的区别详解
Jul 08 Python
django用户登录验证的完整示例代码
Jul 21 Python
pycharm 安装JPype的教程
Aug 08 Python
Django Serializer HiddenField隐藏字段实例
Mar 31 Python
Python Django框架介绍之模板标签及模板的继承
May 27 Python
Python与C++中梯度方向直方图的实现
Mar 17 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
Netflix将与CLAMP、乙一以及冲方丁等6名知名制作人合伙展开原创动画计划!
2020/03/06 日漫
PHP中设置时区,记录日志文件的实现代码
2013/01/07 PHP
自适应图片大小的弹出窗口
2006/07/27 Javascript
JavaScript Base64编码和解码,实现URL参数传递。
2006/09/18 Javascript
javascript 当前日期转化为中文的实现代码
2010/05/13 Javascript
Dom 学习总结以及实例的使用介绍
2013/04/24 Javascript
一个简单的弹性返回顶部JS代码实现介绍
2013/06/09 Javascript
JQuery each打印JS对象的方法
2013/11/13 Javascript
jquery遍历checkbox介绍
2014/02/21 Javascript
多种方法实现360浏览器下禁止自动填写用户名密码
2014/06/16 Javascript
PHPExcel中的一些常用方法汇总
2015/01/23 Javascript
Angular工具方法学习
2016/12/26 Javascript
JSON键值对序列化和反序列化解析
2017/01/24 Javascript
JS+html5 canvas实现的简单绘制折线图效果示例
2017/03/13 Javascript
bootstrap Table服务端处理分页(后台是.net)
2017/10/19 Javascript
bootstrap-Treeview实现级联勾选
2017/11/23 Javascript
基于VuePress 轻量级静态网站生成器的实现方法
2018/04/17 Javascript
详解JavaScript实现动态的轮播图效果
2019/04/29 Javascript
Vue3.0数据响应式原理详解
2019/10/09 Javascript
Vue +WebSocket + WaveSurferJS 实现H5聊天对话交互的实例
2020/11/18 Vue.js
解决vue使用vant轮播组件swipe + flex时文字抖动问题
2021/01/07 Vue.js
[03:04]2018年国际邀请赛典藏宝瓶&莱恩声望物品展示 片尾有彩蛋
2018/06/04 DOTA
跟老齐学Python之有容乃大的list(1)
2014/09/14 Python
Python合并两个字典的常用方法与效率比较
2015/06/17 Python
pandas groupby 分组取每组的前几行记录方法
2018/04/20 Python
python读取目录下所有的jpg文件,并显示第一张图片的示例
2019/06/13 Python
如何使用python的ctypes调用医保中心的dll动态库下载医保中心的账单
2020/05/24 Python
伦敦最著名的老字号百货公司:Selfridges(塞尔福里奇百货)
2016/07/25 全球购物
日本非常有名的内衣丝袜品牌:GUNZE
2017/01/06 全球购物
Asics日本官网:鬼冢八喜郎创立的跑鞋运动品牌
2017/10/18 全球购物
应届生会计求职信
2013/11/11 职场文书
有针对性的求职自荐信
2013/11/14 职场文书
小学生演讲稿
2014/01/12 职场文书
机械工程师岗位职责
2014/06/16 职场文书
驳回起诉民事裁定书
2015/05/19 职场文书
苦儿流浪记读书笔记
2015/07/01 职场文书