Python实现访问者模式

假设要实现一个存放多种类型数据结构的对象,比如一个存放算术操作数和操作符的树结点,需要存放包含一元操作符、二元操作符和数字类型的结点

class Node:passclass UnaryOperator(Node):def __init__(self, operand):self.operand = operandclass BinaryOperator(Node):def __init__(self, left, right):self.left = leftself.right = rightclass Add(BinaryOperator):passclass Sub(BinaryOperator):passclass Mul(BinaryOperator):passclass Div(BinaryOperator):passclass Negative(UnaryOperator):passclass Number(Node):def __init__(self, value):self.value = value

执行运算需要这样调用

# 假设运算式子:2 - (2+2) * 2 / 1 = 2-(8) = -6.0
t1 = Add(Number(2), Number(2))
t2 = Mul(t1, Number(2))
t3 = Div(t2, Number(1))
t4 = Sub(Number(2), t3)

或者这样调用

t5 = Sub(Number(2), Div(Mul(Add(Number(2), Number(2)), Number(2)), Number(1)))

这样子需要执行多次类的调用,极不易读写且冗长,有没有一种方法让调用更加通用,访问变得简单呢。这里使用访问者模式可以达到这样的目的。

访问者模式能够在不改变元素所属对象结构的情况下操作元素,让调用或调用者(caller)的方式变得简单,这种操作常见于的士公司操作,当一个乘客叫了一辆的士时,的士公司接收到了一个访问者,并分配一辆的士去接这个乘客。

首先定义一个访问者结点类VisitorNode,实现最基本的访问入口,任何访问的方式都需要继承这个访问者结点类,并通过这个访问者结点类的visit()方法来访问它的各种操作

# 访问者节点的基类
class NodeVisitor:def visit(self, node):if not isinstance(node, Node):  # 不是Node对象时当做一个值返回,如果有其他情况可以根据实际来处理return nodeself.meth = "visit_" + type(node).__name__.lower()  # type(node)也可以换成node.__class__(只要node.__class__不被篡改)meth = getattr(self, self.meth, None)  if meth is None:meth = self.generic_visitreturn meth(node)def generic_visit(self, node):raise RuntimeError(f"No {self.meth} method")# (一种)访问者对应的类
class Visitor(NodeVisitor):"""方法的名称定义都要与前面定义过的结点类(Node)的名称保证一致性"""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_negative(self, node):  # 如果class Negative 命名-> class Neg,那么 def visit_negative 命名-> def visit_negreturn -self.visit(node.operand)def visit_number(self, node):return node.value

这里的meth = getattr(self, self.meth, None)使用了字符串调用对象方法,self.meth动态地根据各类Node类(Add, Sub, Mul…)的名称定义了对应于类Visitor中的方法(visit_add, visit_sub, visit_mul…)简化了访问入口的代码,当没有获取到对应的方法时会执行generic_visit()并抛出RuntimeError的异常提示访问过程中的异常

如果需要添加一种操作,比如取绝对值,只需要定义一个类class Abs(Unaryoperator): pass并在类Visitor中定义一个visit_abs(self, node)方法即可,不需要做出任何多余的修改,更不需要改变存储的结构

这里visit()方法调用了visit_xxx()方法,而visit_xxx()可能也调用了visit(),本质上是visit()的循环递归调用,当数据量变大时,效率会变得很慢,且递归层次过深时会导致超过限制而失败,而下面介绍的就是利用栈和生成器来消除递归提升效率的实现访问者模式的方法

import typesclass Node:passclass BinaryOperator(Node):def __init__(self, left, right):self.left = leftself.right = rightclass UnaryOperator(Node):def __init__(self, operand):self.operand = operandclass Add(BinaryOperator):passclass Sub(BinaryOperator):passclass Mul(BinaryOperator):passclass Div(BinaryOperator):passclass Negative(UnaryOperator):passclass Number(Node):def __init__(self, value):  # 与UnaryOperator区别仅命名不同self.value = valueclass NodeVisitor:def visit(self, node):# 使用栈+生成器来替换原来visit()的递归写法stack = [node]last_result = None  # 执行一个操作最终都会返回一个值while stack:last = stack[-1]try:if isinstance(last, Node):stack.append(self._visit(stack.pop()))elif isinstance(last, types.GeneratorType):   # GeneratorType会是上一个if返回的对象,这个对象会返回两个node执行算术之后的结果# 如果是生成器,不pop掉,而是不断send,直到StopIteration# 如果last_result不是None,这个值会给回到生成器(例如2被visit_add()的左值接收到)stack.append(last.send(last_result))last_result = Noneelse:   # 计算结果是一个值last_result = stack.pop()except StopIteration:   # 生成器yield结束stack.pop()return last_resultdef _visit(self, node):self.method_name = "visit_" + type(node).__name__.lower()method = getattr(self, self.method_name, None)if method is None:self.generic_visit(node)return method(node)def generic_visit(self, node):raise RuntimeError(f"No {self.method_name} method")class Visitor(NodeVisitor):def visit_add(self, node):yield (yield node.left) + (yield node.right)    # node.left和node.right都可能是Nodedef visit_sub(self, node):yield (yield node.left) - (yield node.right)def visit_mul(self, node):yield (yield node.left) * (yield node.right)def visit_div(self, node):yield (yield node.left) / (yield node.right)def visit_negative(self, node):yield -(yield node.operand)def visit_number(self, node):return node.value

测试是否还会引起超过递归层数的异常

def test_time_cost():import times = time.perf_counter()a = Number(0)for n in range(1, 100000):a = Add(a, Number(n))v = Visitor()print(v.visit(a))print(f"time cost:{time.perf_counter() - s}")

输出正常,没有问题

4999950000
time cost:0.9547078


最后琢磨出了一个似乎可以作为替代的方法

class Node:passclass UnaryOperator(Node):def __init__(self, operand):self.operand = operandclass BinaryOperator(Node):def __init__(self, left, right):self.left = leftself.right = rightclass Add(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value + self.right.valueclass Sub(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value - self.right.valueclass Mul(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value * self.right.valueclass Div(BinaryOperator):def __init__(self, left, right):super().__init__(left, right)self.value = self.left.value / self.right.valueclass Negative(UnaryOperator):def __init__(self, operand):super().__init__(operand)self.value = -self.operand.valueclass Number(Node):def __init__(self, value):self.value = value

运行测试

def test_time_cost():import times = time.perf_counter()a = Number(0)for n in range(1, 100000):a = Add(a, Number(n))print(a.value)print(time.perf_counter() - s)

输出

4999950000
0.2506986

比前面的访问者模式还快而且不用递归,- -!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/469415.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BZOJ 2822: [AHOI2012]树屋阶梯 [Catalan数 高精度]

2822: [AHOI2012]树屋阶梯 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 779 Solved: 453[Submit][Status][Discuss]Description 暑假期间,小龙报名了一个模拟野外生存作战训练班来锻炼体魄,训练的第一个晚上,教官就给他们出了个难题。由…

Python缓存类实例

本篇文章的内容主要包含 利用Python弱引用存储字典缓存类的实例,让参数相同的实例不用重复生成略过复杂的通用化代码编写,利用Python自带库来缓存实例和方法对象 在Python的许多库中都有缓存实例的例子,比如logging模块的Logger类实例 imp…

【Pytorch神经网络理论篇】 06 神经元+神经网络模型+全连接网络模型

同学你好!本文章于2021年末编写,获得广泛的好评! 故在2022年末对本系列进行填充与更新,欢迎大家订阅最新的专栏,获取基于Pytorch1.10版本的理论代码(2023版)实现, Pytorch深度学习理论篇(2023版)目录地址…

面试题:N皇后问题,思路和python解题笔记

n皇后问题算法思路和python解法 问题描述 n皇后问题,在nn的棋盘上,解出n个皇后所有不能互相攻击的摆法, 皇后在数组中用“Q”表示,空地用“.”表示 返回的数据结构格式要求:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…

【Pytorch神经网络理论篇】 07 激活函数+Sigmoid+tanh+ReLU+Swish+Mish+GELU

同学你好!本文章于2021年末编写,获得广泛的好评! 故在2022年末对本系列进行填充与更新,欢迎大家订阅最新的专栏,获取基于Pytorch1.10版本的理论代码(2023版)实现, Pytorch深度学习理论篇(2023版)目录地址…

截图命令

adb shell /system/bin/screencap -p /sdcard/screenshot.png adb pull /sdcard/screenshot.png E:\

【Pytorch神经网络理论篇】 08 Softmax函数(处理分类问题)

同学你好!本文章于2021年末编写,获得广泛的好评! 故在2022年末对本系列进行填充与更新,欢迎大家订阅最新的专栏,获取基于Pytorch1.10版本的理论代码(2023版)实现, Pytorch深度学习理论篇(2023版)目录地址…

Python套接字编程Socket Progaming——1

本篇文章是Network And Web Programing-Socket Programing分类中的第一篇文章,内容主要包含 Socket概念理解Socket programing介绍一个简单的TCP协议的server-client程序支持同时处理多个客户端简单server-client连接程序socket的常用选项使用 理解socket概念 一…

【Pytorch神经网络理论篇】 09 神经网络模块中的损失函数

同学你好!本文章于2021年末编写,获得广泛的好评! 故在2022年末对本系列进行填充与更新,欢迎大家订阅最新的专栏,获取基于Pytorch1.10版本的理论代码(2023版)实现, Pytorch深度学习理论篇(2023版)目录地址…

jquery hover事件中 fadeIn和fadeOut 效果不能及时停止

$(".nav ul li").hover(function () {var id $(this).attr("id");$(".nav dl").each(function (index, domEle) {if ($(domEle).attr("id") id) {$(domEle).fadeIn();}else {$(domEle).stop().fadeOut();//在这里加入.stop() 以阻止…

【Pytorch神经网络理论篇】 10 优化器模块+退化学习率

同学你好!本文章于2021年末编写,获得广泛的好评! 故在2022年末对本系列进行填充与更新,欢迎大家订阅最新的专栏,获取基于Pytorch1.10版本的理论代码(2023版)实现, Pytorch深度学习理论篇(2023版)目录地址…

Android fb0 截屏实现

问题:我们有几个项目,在项目1和项目2上实现截屏是没有问题的,但是在项目3上实现截屏是不行的 原因:分辨率差异引起的问题,分辨率长宽一定要是32的整数倍 Dear customer, Sorry for the late reply due to annual leave. I dont think this issue relates with thediff…

Python trino执行hive insert overwrite不生效的问题

使用python的trino包执行insert overwrite,但是overwrite却没有生效的问题 根据trino的官网介绍的insert overwrite的开启方式,开启hive的insert overwrite会话,使当前会话的insert into语句支持insert overwrite,也即支持插入数…

HAProxy负载均衡原理及企业级实例部署haproxy集群

HAProxy是一种高效、可靠、免费的高可用及负载均衡解决方案,非常适合于高负载站点的七层数据请求。客户端通过HAProxy代理服务器获得站点页面,而代理服务器收到客户请求后根据负载均衡的规则将请求数据转发给后端真实服务器。 同一客户端访问服务器&…

【Pytorch神经网络实战案例】07 预测泰坦尼克号上生存的乘客

1 样本处理 1.1 载入样本代码---Titanic forecast.py(第1部分) import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from scipy import stats import pandas as pd import matplotlib.pyplot as plt import os o…

ubuntu下 安装 adb

1、把adb tool工具考到你要安装的目录夏目 <

基于sanic的服务使用celery完成动态修改定时任务

首先声明一下 考虑到celery目前和asyncio的不兼容性&#xff0c;协程任务需要转换为非异步的普通方法才能被当做task加入定时&#xff0c;并且celery和asyncio使用可能会带来预想不到的问题&#xff0c;在celery官方第二次承诺的6.0版本融合asyncio之前&#xff0c;需要慎重考虑…

shell 中的ifeq

libs_for_gcc -lgnunormal_libs foo: $(objects)ifeq ($(CC),gcc)$(CC) -o foo $(objects) $(libs_for_gcc)else$(CC) -o foo $(objects) $(normal_libs)endif 可见&#xff0c;在上面示例的这个规则中&#xff0c;目标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编…

第一篇unity

在网上找的学习资料&#xff0c;做了点简单的效果。 半成品 http://files.cnblogs.com/files/buzhidaojiaoshenme/unity.rar 第二个游戏&#xff0c;方向键和“W”&#xff0c;”S“键移动方块&#xff0c;碰撞到最右边的方块过关。 http://files.cnblogs.com/files/buzhidaoji…

报错:OMP: Error #15: Initializing libomp.dylib, but found libiomp5.dylib already initialized.

问题描述&#xff1a; OMP: Error #15: Initializing libiomp5.dylib, but found libiomp5.dylib already initialized. OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade perf…