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追加元素到列表的方法
Jul 28 Python
Python多进程分块读取超大文件的方法
Apr 13 Python
Python中使用多进程来实现并行处理的方法小结
Aug 09 Python
python3实现SMTP发送邮件详细教程
Jun 19 Python
python实现顺序表的简单代码
Sep 28 Python
Django之模型层多表操作的实现
Jan 08 Python
python selenium实现发送带附件的邮件代码实例
Dec 10 Python
Numpy 理解ndarray对象的示例代码
Apr 03 Python
PyCharm最新激活码PyCharm2020.2.3有效
Nov 18 Python
python中threading和queue库实现多线程编程
Feb 06 Python
python实现Nao机器人的单目测距
Sep 04 Python
Python创建SQL数据库流程逐步讲解
Sep 23 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
CentOS系统中PHP安装扩展的方式汇总
2017/04/09 PHP
PHP实现找出链表中环的入口节点
2018/01/16 PHP
Laravel创建数据库表结构的例子
2019/10/09 PHP
Prototype源码浅析 String部分(三)之HTML字符串处理
2012/01/15 Javascript
js hover 定时器(实例代码)
2013/11/12 Javascript
JavaScript获取table中某一列的值的方法
2014/05/06 Javascript
jQuery的deferred对象详解
2014/11/12 Javascript
在JavaScript的正则表达式中使用exec()方法
2015/06/16 Javascript
jQuery+Pdo编写login登陆界面
2016/08/01 Javascript
vue.js初学入门教程(2)
2016/11/07 Javascript
js实现hashtable的赋值、取值、遍历操作实例详解
2016/12/25 Javascript
angular ng-click防止重复提交实例
2017/06/16 Javascript
AngularJS实现的输入框字数限制提醒功能示例
2017/10/26 Javascript
vue+vue-validator 表单验证功能的实现代码
2017/11/13 Javascript
js module大战
2019/04/19 Javascript
解决layer.open后laydate失效的问题
2019/09/06 Javascript
webpack.DefinePlugin与cross-env区别详解
2020/02/23 Javascript
uni-app微信小程序登录授权的实现
2020/05/22 Javascript
解决Vue watch里调用方法的坑
2020/11/07 Javascript
Vue如何跨组件传递Slot的实现
2020/12/14 Vue.js
python调用shell的方法
2013/11/20 Python
Python中使用select模块实现非阻塞的IO
2015/02/03 Python
python使用knn实现特征向量分类
2018/12/26 Python
Python简单处理坐标排序问题示例
2019/07/11 Python
css3的focus-within选择器的使用
2020/05/11 HTML / CSS
YOOX美国官方网站:全球著名的多品牌时尚网络概念店
2016/09/11 全球购物
英国鲜花速递:Serenata Flowers
2018/04/03 全球购物
台湾屈臣氏网路商店:Watsons台湾
2020/12/29 全球购物
幼儿园园长岗位职责
2013/11/26 职场文书
申论倡议书范文
2014/05/13 职场文书
先进班组事迹材料
2014/12/25 职场文书
2015年城市管理工作总结
2015/05/23 职场文书
发票退票证明
2015/06/24 职场文书
Tensorflow与RNN、双向LSTM等的踩坑记录及解决
2021/05/31 Python
如何创建一个创建MySQL数据库中的datetime类型
2022/03/21 MySQL
浅谈Node的内存泄露问题
2022/05/06 NodeJs