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 16 Python
Django集成百度富文本编辑器uEditor攻略
Jul 04 Python
Python对象体系深入分析
Oct 28 Python
详解Python中映射类型(字典)操作符的概念和使用
Aug 19 Python
python pandas中对Series数据进行轴向连接的实例
Jun 08 Python
详解将Django部署到Centos7全攻略
Sep 26 Python
PyCharm 配置远程python解释器和在本地修改服务器代码
Jul 23 Python
TensorFlow 显存使用机制详解
Feb 03 Python
Python实现桌面翻译工具【新手必学】
Feb 12 Python
Python如何把十进制数转换成ip地址
May 25 Python
一篇文章教你用python画动态爱心表白
Nov 22 Python
关于python爬虫应用urllib库作用分析
Sep 04 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
MySql中正则表达式的使用方法描述
2008/07/30 PHP
php中url函数介绍及使用示例
2014/02/13 PHP
Yii框架中jquery表单验证插件用法示例
2016/10/18 PHP
php5.x禁用eval的操作方法
2018/10/19 PHP
ThinkPHP框架实现的微信支付接口开发完整示例
2019/04/10 PHP
$(document).ready(function() {})不执行初始化脚本
2014/06/19 Javascript
jQuery实现MSN中文网滑动Tab菜单效果代码
2015/09/09 Javascript
jQuery Ajax File Upload实例源码
2016/12/12 Javascript
元素全屏的设置与监听实例
2017/11/28 Javascript
详解ES6通过WeakMap解决内存泄漏问题
2018/03/09 Javascript
详解vue文件中使用echarts.js的两种方式
2018/10/18 Javascript
使用JavaScript解析URL的方法示例
2019/03/01 Javascript
jQuery/JS监听input输入框值变化实例
2019/10/17 jQuery
js实现小时钟效果
2020/03/25 Javascript
Python简单实现安全开关文件的两种方式
2016/09/19 Python
Python将多个excel文件合并为一个文件
2018/01/03 Python
python覆盖写入,追加写入的实例
2019/06/26 Python
通过python实现弹窗广告拦截过程详解
2019/07/10 Python
postman模拟访问具有Session的post请求方法
2019/07/15 Python
用django设置session过期时间的方法解析
2019/08/05 Python
对django2.0 关联表的必填on_delete参数的含义解析
2019/08/09 Python
基于Django实现日志记录报错信息
2019/12/17 Python
python3 实现调用串口功能
2019/12/26 Python
python 实现有道翻译功能
2021/02/26 Python
摄影展策划方案
2014/06/02 职场文书
党员自我剖析材料范文
2014/10/06 职场文书
搞笑婚前保证书
2015/02/28 职场文书
2015大学生自我评价范文
2015/03/03 职场文书
2015年推广普通话演讲稿
2015/03/20 职场文书
创卫工作总结2015
2015/04/22 职场文书
综治目标管理责任书
2015/05/11 职场文书
新入职员工工作总结
2015/10/15 职场文书
《大禹治水》教学反思
2016/02/22 职场文书
2016年劳模先进事迹材料
2016/02/25 职场文书
Go 语言结构实例分析
2021/07/04 Golang
html5 录制mp3音频支持采样率和比特率设置
2021/07/15 Javascript