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字符串关键点
Dec 13 Python
Python进程间通信之共享内存详解
Oct 30 Python
python爬取网易云音乐评论
Nov 16 Python
Python3.4学习笔记之列表、数组操作示例
Mar 01 Python
python中类的输出或类的实例输出为这种形式的原因
Aug 12 Python
Pyorch之numpy与torch之间相互转换方式
Dec 31 Python
Django重设Admin密码过程解析
Feb 10 Python
python 常见的排序算法实现汇总
Aug 21 Python
基于Python的图像阈值化分割(迭代法)
Nov 20 Python
利用python为PostgreSQL的表自动添加分区
Jan 18 Python
Python爬虫基础讲解之请求
May 13 Python
Python连接Postgres/Mysql/Mongo数据库基本操作大全
Jun 29 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+mysql)
2007/11/23 PHP
浅析十款PHP开发框架的对比
2013/07/05 PHP
什么是PEAR?什么是PECL?PHP中两个容易混淆的概念解释
2015/07/01 PHP
PHP单例模式模拟Java Bean实现方法示例
2018/12/07 PHP
PHP实现基于状态的责任链审批模式详解
2019/05/31 PHP
javascript中setTimeout和setInterval的unref()和ref()用法示例
2014/11/26 Javascript
功能强大的Bootstrap效果展示(二)
2016/08/03 Javascript
EasyUI创建对话框的两种方式
2016/08/23 Javascript
微信小程序 缓存(本地缓存、异步缓存、同步缓存)详解
2017/01/17 Javascript
原生js获取浏览器窗口及元素宽高常用方法集合
2017/01/18 Javascript
一次围绕setTimeout的前端面试经验分享
2017/06/15 Javascript
解决vue 路由变化页面数据不刷新的问题
2018/03/13 Javascript
vue iview组件表格 render函数的使用方法详解
2018/03/15 Javascript
webpack4 从零学习常用配置(小结)
2019/05/28 Javascript
Vue v-bind动态绑定class实例方法
2020/01/15 Javascript
解决Vue @submit 提交后不刷新页面问题
2020/07/18 Javascript
[52:20]VP vs VG Supermajor小组赛 B组胜者组决赛 BO3 第一场 6.2
2018/06/03 DOTA
[45:17]DOTA2-DPC中国联赛定级赛 Phoenix vs DLG BO3第三场 1月9日
2021/03/11 DOTA
Python中用于返回绝对值的abs()方法
2015/05/14 Python
python3序列化与反序列化用法实例
2015/05/26 Python
python 第三方库的安装及pip的使用详解
2017/05/11 Python
Python随机生成手机号、数字的方法详解
2017/07/21 Python
浅谈Python2、Python3相对路径、绝对路径导入方法
2018/06/22 Python
PyQt5 如何让界面和逻辑分离的方法
2020/03/24 Python
python制作一个简单的gui 数据库查询界面
2020/11/19 Python
医疗保健专业人士购物网站:Scrubs & Beyond
2017/02/08 全球购物
澳大利亚最大的护发和护肤品购物网站:RY
2019/12/26 全球购物
太太口服液广告词
2014/03/20 职场文书
岗位竞聘演讲稿范文
2014/04/24 职场文书
保护环境倡议书300字
2014/05/19 职场文书
公安派出所所长四风问题个人对照检查材料
2014/10/04 职场文书
自查自纠整改报告
2014/11/06 职场文书
顶岗实习计划书
2015/01/16 职场文书
通报表扬范文
2015/01/17 职场文书
租车协议书
2015/01/27 职场文书
python执行js代码的方法
2021/05/13 Python