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建立网络客户端
Jun 09 Python
Python实现根据指定端口探测服务器/模块部署的方法
Aug 25 Python
python dict 字典 以及 赋值 引用的一些实例(详解)
Jan 20 Python
python中字符串的操作方法大全
Jun 03 Python
pygame实现简易飞机大战
Sep 11 Python
[原创]Python入门教程3. 列表基本操作【定义、运算、常用函数】
Oct 30 Python
Django 视图层(view)的使用
Nov 09 Python
详解Python 中sys.stdin.readline()的用法
Sep 12 Python
python的pyecharts绘制各种图表详细(附代码)
Nov 11 Python
使用Python对Dicom文件进行读取与写入的实现
Apr 20 Python
opencv深入浅出了解机器学习和深度学习
Mar 17 Python
PyCharm 配置SSH和SFTP连接远程服务器
May 11 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调用三种数据库的方法(1)
2006/10/09 PHP
php中使用ExcelFileParser处理excel获得数据(可作批量导入到数据库使用)
2010/08/21 PHP
关于尾递归的使用详解
2013/05/02 PHP
Laravel5中contracts详解
2015/03/02 PHP
php实现数组中索引关联数据转换成json对象的方法
2015/07/08 PHP
PHP7新功能总结
2019/04/14 PHP
PHP开发API接口签名生成及验证操作示例
2020/05/27 PHP
简单通用的JS滑动门代码
2008/12/19 Javascript
javascript实现tabs选项卡切换效果(自写原生js)
2013/03/19 Javascript
intro.js 页面引导简单用法 分享
2013/08/06 Javascript
兼容ie、firefox的图片自动缩放的css跟js代码分享
2013/08/12 Javascript
Node.js模拟浏览器文件上传示例
2014/03/26 Javascript
JavaScript中提前声明变量或函数例子
2014/11/12 Javascript
《JavaScript DOM 编程艺术》读书笔记之JavaScript 简史
2015/01/09 Javascript
用Move.js配合创建CSS3动画的入门指引
2015/07/22 Javascript
jQuery操作动态生成的内容的方法
2016/05/28 Javascript
bootstrap table实现单击单元格可编辑功能
2017/03/28 Javascript
详解Vue中过度动画效果应用
2017/05/25 Javascript
微信小程序实现多个按钮toggle功能的实例
2017/06/13 Javascript
NodeJs项目中关闭ESLint的方法
2018/08/09 NodeJs
JSON的parse()方法介绍
2019/01/31 Javascript
webpack-url-loader 解决项目中图片打包路径问题
2019/02/15 Javascript
在vue中使用echarts(折线图的demo,markline用法)
2020/07/20 Javascript
django xadmin 管理器常用显示设置方式
2020/03/11 Python
利用python对mysql表做全局模糊搜索并分页实例
2020/07/12 Python
详解Python GUI编程之PyQt5入门到实战
2020/12/10 Python
Alpine安装Python3依赖出现的问题及解决方法
2020/12/25 Python
开放系统互连参考模型
2016/06/29 面试题
Linux文件操作命令都有哪些
2015/02/27 面试题
法律专业应届本科毕业生求职信
2013/10/25 职场文书
留学生如何写好自荐信
2013/12/27 职场文书
小学生感恩演讲稿
2014/04/25 职场文书
小班下学期评语
2014/05/04 职场文书
在职党员进社区活动总结
2014/07/05 职场文书
会议新闻稿
2015/07/17 职场文书
养成教育主题班会
2015/08/13 职场文书