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自动化测试实例解析
Sep 28 Python
使用Python编写一个模仿CPU工作的程序
Apr 16 Python
浅谈Python单向链表的实现
Dec 24 Python
Python编程实现使用线性回归预测数据
Dec 07 Python
基于MTCNN/TensorFlow实现人脸检测
May 24 Python
Python学习笔记基本数据结构之序列类型list tuple range用法分析
Jun 08 Python
Python 图像处理: 生成二维高斯分布蒙版的实例
Jul 04 Python
PyCharm 专业版安装图文教程
Feb 20 Python
Tensorflow全局设置可见GPU编号操作
Jun 30 Python
解决python中0x80072ee2错误的方法
Jul 19 Python
Python使用struct处理二进制(pack和unpack用法)
Nov 12 Python
python numpy中multiply与*及matul 的区别说明
May 26 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通过exif_read_data函数获取图片的exif信息
2015/05/21 PHP
PHP _construct()函数讲解
2019/02/03 PHP
JS 页面内容搜索,类似于 Ctrl+F功能的实现代码
2007/08/13 Javascript
原生js ActiveXObject获取execl里面的值
2013/11/01 Javascript
下拉框select的绑定示例
2014/09/04 Javascript
Vue2递归组件实现树形菜单
2017/04/10 Javascript
vue自定义全局组件(自定义插件)的用法
2018/01/30 Javascript
详解nodejs通过响应回写的方式渲染页面资源
2018/04/07 NodeJs
jQuery实现简易聊天框
2020/02/08 jQuery
如何在postman测试用例中实现断言过程解析
2020/07/09 Javascript
[01:02:03]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS VG
2014/05/26 DOTA
[03:49]2016完美“圣”典风云人物:AMS专访
2016/12/06 DOTA
[49:08]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第一场 11.27
2020/12/01 DOTA
Python编程中字符串和列表的基本知识讲解
2015/10/14 Python
Python数据类型详解(二)列表
2016/05/08 Python
Python中字符串的修改及传参详解
2016/11/30 Python
django Serializer序列化使用方法详解
2018/10/16 Python
Python地图绘制实操详解
2019/03/04 Python
Python Django 添加首页尾页上一页下一页代码实例
2019/08/21 Python
pytorch实现线性拟合方式
2020/01/15 Python
解决python运行效率不高的问题
2020/07/20 Python
CSS3模块的目前的状况分析
2010/02/24 HTML / CSS
html5中canvas学习笔记1-画板的尺寸与实际显示尺寸
2013/01/06 HTML / CSS
HTML5的语法变化介绍
2013/08/13 HTML / CSS
HTML5 在canvas中绘制矩形附效果图
2014/06/23 HTML / CSS
使用html5实现表格实现标题合并的实例代码
2019/05/13 HTML / CSS
美国瑜伽服装和装备购物网站:Mukha Yoga
2019/02/22 全球购物
计算机应用专业应届毕业生中文求职信范文
2013/11/29 职场文书
缴纳养老保险的证明
2014/01/10 职场文书
品质标语大全
2014/06/21 职场文书
放假通知格式
2015/04/14 职场文书
2015年重阳节活动主持词
2015/07/30 职场文书
2016大一新生军训感言
2015/12/08 职场文书
python-opencv 中值滤波{cv2.medianBlur(src, ksize)}的用法
2021/06/05 Python
mybatis 获取无数据的字段不显示的问题
2021/07/15 Java/Android
Django框架中表单的用法
2022/06/10 Python