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 14 Python
Python正则表达式教程之二:捕获篇
Mar 02 Python
Python入门_学会创建并调用函数的方法
May 16 Python
python版简单工厂模式
Oct 16 Python
NumPy 如何生成多维数组的方法
Feb 05 Python
基于DataFrame筛选数据与loc的用法详解
May 18 Python
python 获取页面表格数据存放到csv中的方法
Dec 26 Python
python读csv文件时指定行为表头或无表头的方法
Jun 26 Python
Python搭建代理IP池实现存储IP的方法
Oct 27 Python
Python getsizeof()和getsize()区分详解
Nov 20 Python
Python入门学习之类的相关知识总结
May 25 Python
OpenCV-Python实现怀旧滤镜与连环画滤镜
Jun 09 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
Protoss魔法科技
2020/03/14 星际争霸
用PHP开发GUI
2006/10/09 PHP
浅谈php serialize()与unserialize()的用法
2013/06/05 PHP
php缓冲 output_buffering的使用详解
2013/06/13 PHP
php网页标题中文乱码的有效解决方法
2014/03/05 PHP
php  PATH_SEPARATOR判断当前服务器系统类型实例
2016/10/28 PHP
PHP单例模式定义与使用实例详解
2017/02/06 PHP
JS Timing
2007/04/21 Javascript
js GridView 实现自动计算操作代码
2009/03/25 Javascript
仅IE9/10同时支持script元素的onload和onreadystatechange事件分析
2011/04/27 Javascript
js和css写一个可以自动隐藏的悬浮框
2014/03/05 Javascript
JavaScript实现找质数代码分享
2015/03/24 Javascript
js精准的倒计时函数分享
2016/06/29 Javascript
整理关于Bootstrap表单的慕课笔记
2017/03/29 Javascript
JavaScript mixin实现多继承的方法详解
2017/03/30 Javascript
详解Angular.js数据绑定时自动转义html标签及内容
2017/03/30 Javascript
vuejs使用axios异步访问时用get和post的实例讲解
2018/08/09 Javascript
Node.js 使用axios读写influxDB的方法示例
2018/10/26 Javascript
详解如何在Angular优雅编写HTTP请求
2018/12/05 Javascript
javascript中floor使用方法总结
2019/02/02 Javascript
python 捕获shell脚本的输出结果实例
2017/01/04 Python
Python 数据结构之堆栈实例代码
2017/01/22 Python
Python编程实现双链表,栈,队列及二叉树的方法示例
2017/11/01 Python
Pandas实现数据类型转换的一些小技巧汇总
2018/05/07 Python
解决DataFrame排序sort的问题
2018/06/07 Python
Python定时发送消息的脚本:每天跟你女朋友说晚安
2018/10/21 Python
对Pandas DataFrame缺失值的查找与填充示例讲解
2018/11/06 Python
python scipy求解非线性方程的方法(fsolve/root)
2018/11/12 Python
python合并已经存在的sheet数据到新sheet的方法
2018/12/11 Python
浅谈keras保存模型中的save()和save_weights()区别
2020/05/21 Python
电影T恤、80年代T恤和80年代服装:TV Store Online
2020/01/05 全球购物
《北京的春节》教学反思
2014/04/07 职场文书
2015年幼儿园新年寄语
2014/12/08 职场文书
财务部岗位职责
2015/02/03 职场文书
大学生社区义工服务心得体会
2016/01/22 职场文书
理解深度学习之深度学习简介
2021/04/14 Python