concern
You're dealing with a complex data structure consisting of a large number of different types of objects, each of which needs to require different processing. For example, traversing a tree structure and then performing different operations based on the corresponding state of each node.
prescription
The problem encountered here is common in the field of programming, where a data structure consisting of a large number of different objects is sometimes constructed. Suppose you are writing a program that represents a mathematical expression, then you may need to define the following class:
class Node: pass class UnaryOperator(Node): def __init__(self, operand): = operand class BinaryOperator(Node): def __init__(self, left, right): = left = 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): = value
These classes are then used to construct nested data structures as follows:
# 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)
The problem with this is that for each expression, it has to be redefined every time, is there a more general way to make it support all numbers and operators. Here we can achieve this using the visitor pattern:
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__))
To use this class, you can define a class that inherits it and implements the various visit_Name() methods, where Name is of type node. For example, if you want to find the value of an expression, you can write it like this:
class Evaluator(NodeVisitor): def visit_Number(self, node): return def visit_Add(self, node): return () + () def visit_Sub(self, node): return () - () def visit_Mul(self, node): return () * () def visit_Div(self, node): return () / () def visit_Negate(self, node): return -
Example of use:
>>> e = Evaluator() >>> (t4) 0.6 >>>
As a different example, the following defines a class that converts an expression into multiple sequences of operations on top of a stack:
class StackCode(NodeVisitor): def generate_code(self, node): = [] (node) return def visit_Number(self, node): (('PUSH', )) def binop(self, node, instruction): () () ((instruction,)) def visit_Add(self, node): (node, 'ADD') def visit_Sub(self, node): (node, 'SUB') def visit_Mul(self, node): (node, 'MUL') def visit_Div(self, node): (node, 'DIV') def unaryop(self, node, instruction): () ((instruction,)) def visit_Negate(self, node): (node, 'NEG')
Example of use:
>>> s = StackCode() >>> s.generate_code(t4) [('PUSH', 1), ('PUSH', 2), ('PUSH', 3), ('PUSH', 4), ('SUB',), ('MUL',), ('PUSH', 5), ('DIV',), ('ADD',)] >>>
talk over
In the beginning you may write a lot of if/else statements to implement this, the benefit of the visitor pattern here is to get the appropriate method via getattr() and use recursion to traverse all the nodes:
def binop(self, node, instruction): () () ((instruction,))
Another thing to point out is that this technique is also the way to implement switch or case statements in other languages. For example, if you are writing an HTTP framework, you might write a controller for request distribution like this:
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
One disadvantage of the accessor pattern is that it relies heavily on recursion, which can be problematic if the data structure is nested too deeply, sometimes exceeding Python's recursion depth limit (cf.()
)。
It is very common to use the visitor pattern in programming related to parsing and compilation, and Python's own ast module is worth a look at the source code.
Above is Python how to realize the details of the visitor mode, more information about Python visitor mode please pay attention to my other related articles!