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创建xml的方法
Mar 10 Python
python实现逻辑回归的方法示例
May 02 Python
python 3.5下xadmin的使用及修复源码bug
May 10 Python
Python中浅拷贝copy与深拷贝deepcopy的简单理解
Oct 26 Python
在Python中定义一个常量的方法
Nov 10 Python
python之验证码生成(gvcode与captcha)
Jan 02 Python
Pandas之排序函数sort_values()的实现
Jul 09 Python
简单了解Python读取大文件代码实例
Dec 18 Python
基于torch.where和布尔索引的速度比较
Jan 02 Python
Python多线程实现支付模拟请求过程解析
Apr 21 Python
快速了解Python开发环境Spyder
Jun 29 Python
全网最细 Python 格式化输出用法讲解(推荐)
Jan 18 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采集速度探究总结(原创)
2008/04/18 PHP
php实现猴子选大王问题算法实例
2015/04/20 PHP
Laravel框架实现利用监听器进行sql语句记录功能
2018/06/06 PHP
PHP超级全局变量【$GLOBALS,$_SERVER,$_REQUEST等】用法实例分析
2019/12/11 PHP
6个常见的 PHP 安全性攻击实例和阻止方法
2020/12/16 PHP
解决FLASH需要点击激活的代码
2006/12/20 Javascript
JS模拟的QQ面板上的多级可展开的菜单
2009/10/10 Javascript
javascript实现跨域的方法汇总
2015/06/25 Javascript
JavaScript各类型的关系图解
2015/10/16 Javascript
JavaScript实现点击按钮直接打印
2016/01/06 Javascript
JavaScript每天必学之基础知识
2016/09/17 Javascript
Jquery树插件zTree实现菜单树
2017/01/24 Javascript
微信小程序 页面跳转如何实现传值
2017/04/05 Javascript
利用js编写网页进度条效果
2017/10/08 Javascript
使用weixin-java-tools完成微信授权登录、微信支付的示例
2018/09/26 Javascript
vue实现点击按钮切换背景颜色的示例代码
2020/06/23 Javascript
Python批量修改文件后缀的方法
2014/01/26 Python
判断网页编码的方法python版
2016/08/12 Python
python3实现UDP协议的服务器和客户端
2017/06/14 Python
python 读取文本文件的行数据,文件.splitlines()的方法
2018/07/12 Python
解决Python内层for循环如何break出外层的循环的问题
2019/06/24 Python
Python实现银行账户资金交易管理系统
2020/01/03 Python
Python如何使用paramiko模块连接linux
2020/03/18 Python
django实现模板中的字符串文字和自动转义
2020/03/31 Python
CSS3新增布局之: flex详解
2020/06/18 HTML / CSS
新闻专业本科生的自我评价分享
2013/11/20 职场文书
七一表彰活动方案
2014/01/18 职场文书
致垒球运动员加油稿
2014/02/16 职场文书
晨会主持词
2014/03/17 职场文书
企业道德讲堂实施方案
2014/03/19 职场文书
青春无悔演讲稿
2014/05/08 职场文书
致运动员加油稿
2015/07/21 职场文书
nginx限制并发连接请求数的方法
2021/04/01 Servers
Python基础之hashlib模块详解
2021/05/06 Python
Python中的min及返回最小值索引的操作
2021/05/10 Python
Python爬虫网络请求之代理服务器和动态Cookies
2022/04/12 Python