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编码时应该注意的几个情况
Mar 04 Python
Python pass 语句使用示例
Mar 11 Python
python中的全局变量用法分析
Jun 09 Python
python编程实现归并排序
Apr 14 Python
python 计算数组中每个数字出现多少次--“Bucket”桶的思想
Dec 19 Python
python实现决策树分类算法
Dec 21 Python
python中类的属性和方法介绍
Nov 27 Python
使用pip安装python库的多种方式
Jul 31 Python
wxPython之wx.DC绘制形状
Nov 19 Python
浅谈Python的方法解析顺序(MRO)
Mar 05 Python
python 用递归实现通用爬虫解析器
Apr 16 Python
Python可变集合和不可变集合的构造方法大全
Dec 06 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 HTML代码串 截取实现代码
2009/06/29 PHP
php+js iframe实现上传头像界面无跳转
2014/04/29 PHP
PHP中Memcache操作类及用法实例
2014/12/12 PHP
PHP实现清除MySQL死连接的方法
2016/07/23 PHP
PHP入门教程之会话控制技巧(cookie与session)
2016/09/11 PHP
PHP基于堆栈实现的高级计算器功能示例
2017/09/15 PHP
详解PHP中的8个魔术常量
2020/07/06 PHP
JavaScript 直接操作本地文件的实现代码
2009/12/01 Javascript
jQuery中append、insertBefore、after与insertAfter的简单用法与注意事项
2020/04/04 Javascript
JavaScript中把数字转换为字符串的程序代码
2013/06/19 Javascript
一个JS的日期格式化算法示例
2013/07/31 Javascript
js showModalDialog弹出窗口实例详解
2014/01/07 Javascript
jquery ajax请求方式与提示用户正在处理请稍等
2014/09/01 Javascript
ExtJs动态生成treepanel的Json格式
2015/07/19 Javascript
js学习笔记之事件处理模型
2016/10/31 Javascript
B/S(Web)实时通讯解决方案分享
2017/04/06 Javascript
vue计算属性和监听器实例解析
2018/05/10 Javascript
vue点击input弹出带搜索键盘并监听该元素的方法
2018/08/25 Javascript
微信小程序实现点击卡片 翻转效果
2019/09/04 Javascript
浅谈vue中resetFields()使用注意事项
2020/08/12 Javascript
[03:56]显微镜下的DOTA2第十一期——鬼畜的死亡先知播音员
2014/06/23 DOTA
[40:04]Secret vs Infamous 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.23
2019/09/05 DOTA
python使用分治法实现求解最大值的方法
2015/05/12 Python
Python 统计字数的思路详解
2018/05/08 Python
python matplotlib包图像配色方案分享
2020/03/14 Python
基于SQLAlchemy实现操作MySQL并执行原生sql语句
2020/06/10 Python
几个常见的软件测试问题
2016/09/07 面试题
车间班长岗位职责
2013/11/30 职场文书
满月酒答谢词
2014/01/14 职场文书
活动总结新闻稿
2014/08/30 职场文书
夫妻忠诚协议范文
2014/11/16 职场文书
学生个人评语大全
2015/01/04 职场文书
2015个人简历自我评价语
2015/03/11 职场文书
企业工会工作总结2015
2015/05/13 职场文书
解决golang结构体tag编译错误的问题
2021/05/02 Golang
SQL Server代理:理解SQL代理错误日志处理方法
2021/06/30 SQL Server