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的Template使用指南
Sep 11 Python
Python并发编程协程(Coroutine)之Gevent详解
Dec 27 Python
python实现聚类算法原理
Feb 12 Python
对Python 3.5拼接列表的新语法详解
Nov 08 Python
Django REST framework视图的用法
Jan 16 Python
Python中常用的8种字符串操作方法
May 06 Python
Python定时任务APScheduler的实例实例详解
Jul 22 Python
用python生成与调用cntk模型代码演示方法
Aug 26 Python
python中利用matplotlib读取灰度图的例子
Dec 07 Python
Python with语句用法原理详解
Jul 03 Python
Python读取yaml文件的详细教程
Jul 21 Python
Pytest中skip skipif跳过用例详解
Jun 30 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
《星际争霸2》终章已出 RTS时代宣告终结
2017/02/07 星际争霸
PHP网页游戏学习之Xnova(ogame)源码解读(九)
2014/06/24 PHP
PHP命名空间(namespace)的动态访问及使用技巧
2014/08/18 PHP
10款实用的PHP开源工具
2015/10/23 PHP
将函数的实际参数转换成数组的方法
2010/01/25 Javascript
AngularJs定制样式插入到ueditor中的问题小结
2016/08/01 Javascript
JavaScript中正则表达式判断匹配规则及常用方法
2017/08/03 Javascript
highCharts提示框中显示当前时间的方法
2019/01/18 Javascript
Angular2实现的秒表及改良版示例
2019/05/10 Javascript
微信小程序封装的HTTP请求示例【附升级版】
2019/05/11 Javascript
深入理解基于vue-cli的webpack打包优化实践及探索
2019/10/14 Javascript
vuex存值与取值的实例
2019/11/06 Javascript
安装多版本Vue-CLI的实现方法
2020/03/24 Javascript
vue keep-alive的简单总结
2021/01/25 Vue.js
Python数据类型学习笔记
2016/01/13 Python
python利用MethodType绑定方法到类示例代码
2017/08/27 Python
TensorFlow实现Logistic回归
2018/09/07 Python
python TKinter获取文本框内容的方法
2018/10/11 Python
python  Django中的apps.py的目的是什么
2018/10/15 Python
python dict 相同key 合并value的实例
2019/01/21 Python
Django Rest framework权限的详细用法
2019/07/25 Python
python计算Content-MD5并获取文件的Content-MD5值方式
2020/04/03 Python
selenium+python实现基本自动化测试的示例代码
2021/01/27 Python
使用HTML5拍照示例代码
2013/08/06 HTML / CSS
英国知名的护肤彩妆与时尚配饰大型综合零售电商:Unineed
2016/11/21 全球购物
Crocs美国官方网站:卡骆驰洞洞鞋
2017/08/04 全球购物
学院书画协会部门职责
2013/11/28 职场文书
教育科研先进个人材料
2014/01/26 职场文书
函授本科自我鉴定
2014/02/04 职场文书
学校食堂食品安全责任书
2014/07/28 职场文书
校园文化艺术节宣传标语
2014/10/09 职场文书
会计求职信怎么写
2015/03/20 职场文书
汽车销售助理岗位职责
2015/04/14 职场文书
2015年乡镇流动人口工作总结
2015/05/12 职场文书
运动会开幕式致辞
2015/07/29 职场文书
Nginx工作模式及代理配置的使用细节
2022/03/21 Servers