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爬虫入门教程--优雅的HTTP库requests(二)
May 25 Python
python基础_文件操作实现全文或单行替换的方法
Sep 04 Python
python使用SMTP发送qq或sina邮件
Oct 21 Python
Python3数据库操作包pymysql的操作方法
Jul 16 Python
Pytorch Tensor 输出为txt和mat格式方式
Jan 03 Python
Tensorflow不支持AVX2指令集的解决方法
Feb 03 Python
python GUI库图形界面开发之PyQt5控件数据拖曳Drag与Drop详细使用方法与实例
Feb 27 Python
python获取响应某个字段值的3种实现方法
Apr 30 Python
python函数map()和partial()的知识点总结
May 26 Python
Python实现数字的格式化输出
Aug 01 Python
Python限制内存和CPU使用量的方法(Unix系统适用)
Aug 04 Python
如何通过一篇文章了解Python中的生成器
Apr 02 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 程序员的调试技术小结
2009/11/15 PHP
php之XML转数组函数的详解
2013/06/07 PHP
ie和firefox中img对象区别的困惑
2006/12/27 Javascript
在chrome浏览器中,防止input[text]和textarea在聚焦时出现黄色边框的解决方法
2011/05/24 Javascript
JavaScript 原型继承之构造函数继承
2011/08/26 Javascript
解析js如何获取当前url中的参数值并复制给input
2013/06/23 Javascript
jquery 绑定回车动作扑捉回车键触发的事件
2014/03/26 Javascript
JavaScript使用二分查找算法在数组中查找数据的方法
2015/04/07 Javascript
JavaScript实现信用卡校验方法
2015/04/07 Javascript
jQuery获取父元素节点、子元素节点及兄弟元素节点的方法
2016/04/14 Javascript
AngularJs实现分页功能不带省略号的代码
2016/05/30 Javascript
JS导出PDF插件的方法(支持中文、图片使用路径)
2016/07/12 Javascript
解决bootstrap下拉菜单点击立即隐藏bug的方法
2017/06/13 Javascript
解决Mac下安装nmp的淘宝镜像失败问题
2018/05/16 Javascript
Vue 幸运大转盘实现思路详解
2019/05/06 Javascript
微信小程序自定义toast组件的方法详解【含动画】
2019/05/11 Javascript
js绘制一条直线并旋转45度
2020/08/21 Javascript
Vue-cli4 配置 element-ui 按需引入操作
2020/09/11 Javascript
python 域名分析工具实现代码
2009/07/15 Python
举例详解Python中yield生成器的用法
2015/08/05 Python
python进程管理工具supervisor的安装与使用教程
2017/09/05 Python
Python 实现文件打包、上传与校验的方法
2019/02/13 Python
PyCharm2019安装教程及其使用(图文教程)
2019/09/29 Python
如何使用python切换hosts文件
2020/04/29 Python
OpenCV 之按位运算举例解析
2020/06/19 Python
pycharm 代码自动补全的实现方法(图文)
2020/09/18 Python
Etam德国:内衣精品店
2019/08/25 全球购物
《苏珊的帽子》教学反思
2014/04/07 职场文书
产品生产计划书
2014/05/07 职场文书
群众路线教育实践活动批评与自我批评
2014/09/15 职场文书
2014校长四风问题对照检查材料思想汇报
2014/09/16 职场文书
2014各大专业毕业生自我评价
2014/09/17 职场文书
2014年大学学生会工作总结
2014/12/02 职场文书
2015年九一八事变纪念活动实施方案
2015/05/06 职场文书
2015婚礼主持词开场白
2015/05/28 职场文书
创业项目(超低成本创业项目)
2019/08/16 职场文书