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编程-将Python程序转化为可执行程序[整理]
Apr 09 Python
python模拟新浪微博登陆功能(新浪微博爬虫)
Dec 24 Python
详解Python2.x中对Unicode编码的使用
Apr 03 Python
介绍Python的Django框架中的QuerySets
Apr 20 Python
Python复数属性和方法运算操作示例
Jul 21 Python
django解决跨域请求的问题
Nov 11 Python
pygame实现贪吃蛇游戏(上)
Oct 29 Python
Django通过dwebsocket实现websocket的例子
Nov 15 Python
Python生成器常见问题及解决方案
Mar 21 Python
Python填充任意颜色,不同算法时间差异分析说明
May 16 Python
python 常用日期处理-- datetime 模块的使用
Sep 02 Python
python实现手机推送 代码也就10行左右
Apr 12 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启用zlib压缩文件的配置方法
2013/06/12 PHP
tp5(thinkPHP5框架)时间查询操作实例分析
2019/05/29 PHP
测试JavaScript字符串处理性能的代码
2009/12/07 Javascript
分页栏的web标准实现
2011/11/01 Javascript
checkbox使用示例
2013/08/23 Javascript
JS获取select的value和text值的简单实例
2014/02/26 Javascript
浅析JavaScript基本类型与引用类型
2014/05/28 Javascript
js读取并解析JSON类型数据的方法
2015/11/14 Javascript
js获取本机操作系统类型的两种方法
2015/12/19 Javascript
JS如何设置cookie有效期为当天24点并弹出欢迎登陆界面
2016/08/04 Javascript
利用jquery禁止外层滚动条的滚动
2017/01/05 Javascript
Bootstrap 3浏览器兼容性问题及解决方案
2017/04/11 Javascript
React 组件间的通信示例
2018/06/14 Javascript
vue使用Element组件时v-for循环里的表单项验证方法
2018/06/28 Javascript
Angular7中创建组件/自定义指令/管道的方法实例详解
2019/04/02 Javascript
解决Vue的项目使用Element ui 走马灯无法实现的问题
2020/08/03 Javascript
JS实现超级好看的鼠标小尾巴特效
2020/12/01 Javascript
解析Mac OS下部署Pyhton的Django框架项目的过程
2016/05/03 Python
Python错误提示:[Errno 24] Too many open files的分析与解决
2017/02/16 Python
Python实现的括号匹配判断功能示例
2018/08/25 Python
[原创]Python入门教程4. 元组基本操作
2018/10/31 Python
对python_discover方法遍历所有执行的用例详解
2019/02/13 Python
python3.6 如何将list存入txt后再读出list的方法
2019/07/02 Python
Python selenium 自动化脚本打包成一个exe文件(推荐)
2020/01/14 Python
500行python代码实现飞机大战
2020/04/24 Python
python中的split、rsplit、splitlines用法说明
2020/10/23 Python
浅谈css3新单位vw、vh、vmin、vmax的使用详解
2017/12/01 HTML / CSS
YesStyle美国/全球:购买亚洲时装、美容化妆品和生活百货
2017/01/16 全球购物
京东奢侈品:全球奢侈品牌
2018/03/17 全球购物
String和StringBuffer的区别
2015/08/13 面试题
护理专科自荐书范文
2014/02/18 职场文书
机械制造专业大学生自我鉴定
2014/09/19 职场文书
关于随地扔垃圾的检讨书
2014/09/30 职场文书
承租经营合作者协议书
2014/10/01 职场文书
关于办理居住证的介绍信模板
2019/11/27 职场文书
JS实现扫雷项目总结
2021/05/19 Javascript