前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python 访问者模式

python 访问者模式

作者头像
用户5760343
发布2019-12-12 18:03:34
5730
发布2019-12-12 18:03:34
举报
文章被收录于专栏:sktjsktj

这里遇到的问题在编程领域中是很普遍的,有时候会构建一个由大量不同对象组成的数据结构。 假设你要写一个表示数学表达式的程序,那么你可能需要定义如下的类:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">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 </pre>

然后利用这些类构建嵌套数据结构,如下所示:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;"># 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) </pre>

这样做的问题是对于每个表达式,每次都要重新定义一遍,有没有一种更通用的方式让它支持所有的数字和操作符呢。 这里我们使用访问者模式可以达到这样的目的:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">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)

代码语言:javascript
复制
def generic_visit(self, node):
    raise RuntimeError('No {} method'.format('visit_' + type(node).__name__))

</pre>

为了使用这个类,可以定义一个类继承它并且实现各种 visit_Name() 方法,其中Name是node类型。 例如,如果你想求表达式的值,可以这样写:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">class Evaluator(NodeVisitor): def visit_Number(self, node): return node.value

代码语言:javascript
复制
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

</pre>

使用示例:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> e = Evaluator()

e.visit(t4) 0.6

</pre>

作为一个不同的例子,下面定义一个类在一个栈上面将一个表达式转换成多个操作序列:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">class StackCode(NodeVisitor): def generate_code(self, node): self.instructions = [] self.visit(node) return self.instructions

代码语言:javascript
复制
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')

</pre>

使用示例:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">>>> s = StackCode()

s.generate_code(t4) [('PUSH', 1), ('PUSH', 2), ('PUSH', 3), ('PUSH', 4), ('SUB',), ('MUL',), ('PUSH', 5), ('DIV',), ('ADD',)]

</pre>

讨论

刚开始的时候你可能会写大量的if/else语句来实现, 这里访问者模式的好处就是通过 getattr() 来获取相应的方法,并利用递归来遍历所有的节点:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">def binop(self, node, instruction): self.visit(node.left) self.visit(node.right) self.instructions.append((instruction,)) </pre>

还有一点需要指出的是,这种技术也是实现其他语言中switch或case语句的方式。 比如,如果你正在写一个HTTP框架,你可能会写这样一个请求分发的控制器:

<pre style="box-sizing: border-box; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", Courier, monospace; font-size: 12px; white-space: pre; margin: 0px; padding: 12px; display: block; overflow: auto; line-height: 1.4;">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 </pre>

访问者模式一个缺点就是它严重依赖递归,如果数据结构嵌套层次太深可能会有问题, 有时候会超过Python的递归深度限制(参考 sys.getrecursionlimit() )。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 讨论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档