|
5 | 5 | ----------
|
6 | 6 | 问题
|
7 | 7 | ----------
|
8 |
| -todo... |
| 8 | +你要处理由大量不同类型的对象组成的复杂数据结构,每一个对象都需要需要进行不同的处理。 |
| 9 | +比如,遍历一个树形结构,然后根据每个节点的相应状态执行不同的操作。 |
| 10 | + |
| 11 | +| |
9 | 12 |
|
10 | 13 | ----------
|
11 | 14 | 解决方案
|
12 | 15 | ----------
|
13 |
| -todo... |
| 16 | +这里遇到的问题在编程领域中是很普遍的,有时候会构建一个由大量不同对象组成的数据结构。 |
| 17 | +假设你要写一个表示数学表达式的程序,那么你可能需要定义如下的类: |
| 18 | + |
| 19 | +.. code-block:: python |
| 20 | +
|
| 21 | + class Node: |
| 22 | + pass |
| 23 | +
|
| 24 | + class UnaryOperator(Node): |
| 25 | + def __init__(self, operand): |
| 26 | + self.operand = operand |
| 27 | +
|
| 28 | + class BinaryOperator(Node): |
| 29 | + def __init__(self, left, right): |
| 30 | + self.left = left |
| 31 | + self.right = right |
| 32 | +
|
| 33 | + class Add(BinaryOperator): |
| 34 | + pass |
| 35 | +
|
| 36 | + class Sub(BinaryOperator): |
| 37 | + pass |
| 38 | +
|
| 39 | + class Mul(BinaryOperator): |
| 40 | + pass |
| 41 | +
|
| 42 | + class Div(BinaryOperator): |
| 43 | + pass |
| 44 | +
|
| 45 | + class Negate(UnaryOperator): |
| 46 | + pass |
| 47 | +
|
| 48 | + class Number(Node): |
| 49 | + def __init__(self, value): |
| 50 | + self.value = value |
| 51 | +
|
| 52 | +然后利用这些类构建嵌套数据结构,如下所示: |
| 53 | + |
| 54 | +.. code-block:: python |
| 55 | +
|
| 56 | + # Representation of 1 + 2 * (3 - 4) / 5 |
| 57 | + t1 = Sub(Number(3), Number(4)) |
| 58 | + t2 = Mul(Number(2), t1) |
| 59 | + t3 = Div(t2, Number(5)) |
| 60 | + t4 = Add(Number(1), t3) |
| 61 | +
|
| 62 | +这样做的问题是对于每个表达式,每次都要重新定义一遍,有没有一种更通用的方式让它支持所有的数字和操作符呢。 |
| 63 | +这里我们使用观察者模式可以达到这样的目的: |
| 64 | + |
| 65 | +.. code-block:: python |
| 66 | +
|
| 67 | + class NodeVisitor: |
| 68 | + def visit(self, node): |
| 69 | + methname = 'visit_' + type(node).__name__ |
| 70 | + meth = getattr(self, methname, None) |
| 71 | + if meth is None: |
| 72 | + meth = self.generic_visit |
| 73 | + return meth(node) |
| 74 | +
|
| 75 | + def generic_visit(self, node): |
| 76 | + raise RuntimeError('No {} method'.format('visit_' + type(node).__name__)) |
| 77 | +
|
| 78 | +为了使用这个类,可以定义一个类继承它并且实现各种 ``visit_Name()`` 方法,其中Name是node类型。 |
| 79 | +例如,如果你想求表达式的值,可以这样写: |
| 80 | + |
| 81 | +.. code-block:: python |
| 82 | +
|
| 83 | + class Evaluator(NodeVisitor): |
| 84 | + def visit_Number(self, node): |
| 85 | + return node.value |
| 86 | +
|
| 87 | + def visit_Add(self, node): |
| 88 | + return self.visit(node.left) + self.visit(node.right) |
| 89 | +
|
| 90 | + def visit_Sub(self, node): |
| 91 | + return self.visit(node.left) - self.visit(node.right) |
| 92 | +
|
| 93 | + def visit_Mul(self, node): |
| 94 | + return self.visit(node.left) * self.visit(node.right) |
| 95 | +
|
| 96 | + def visit_Div(self, node): |
| 97 | + return self.visit(node.left) / self.visit(node.right) |
| 98 | +
|
| 99 | + def visit_Negate(self, node): |
| 100 | + return -node.operand |
| 101 | +
|
| 102 | +使用示例: |
| 103 | + |
| 104 | +.. code-block:: python |
| 105 | +
|
| 106 | + >>> e = Evaluator() |
| 107 | + >>> e.visit(t4) |
| 108 | + 0.6 |
| 109 | + >>> |
| 110 | +
|
| 111 | +作为一个不同的例子,下面定义一个类在一个栈上面将一个表达式转换成多个操作序列: |
| 112 | + |
| 113 | +.. code-block:: python |
| 114 | +
|
| 115 | + class StackCode(NodeVisitor): |
| 116 | + def generate_code(self, node): |
| 117 | + self.instructions = [] |
| 118 | + self.visit(node) |
| 119 | + return self.instructions |
| 120 | +
|
| 121 | + def visit_Number(self, node): |
| 122 | + self.instructions.append(('PUSH', node.value)) |
| 123 | +
|
| 124 | + def binop(self, node, instruction): |
| 125 | + self.visit(node.left) |
| 126 | + self.visit(node.right) |
| 127 | + self.instructions.append((instruction,)) |
| 128 | +
|
| 129 | + def visit_Add(self, node): |
| 130 | + self.binop(node, 'ADD') |
| 131 | +
|
| 132 | + def visit_Sub(self, node): |
| 133 | + self.binop(node, 'SUB') |
| 134 | +
|
| 135 | + def visit_Mul(self, node): |
| 136 | + self.binop(node, 'MUL') |
| 137 | +
|
| 138 | + def visit_Div(self, node): |
| 139 | + self.binop(node, 'DIV') |
| 140 | +
|
| 141 | + def unaryop(self, node, instruction): |
| 142 | + self.visit(node.operand) |
| 143 | + self.instructions.append((instruction,)) |
| 144 | +
|
| 145 | + def visit_Negate(self, node): |
| 146 | + self.unaryop(node, 'NEG') |
| 147 | +
|
| 148 | +使用示例: |
| 149 | + |
| 150 | +.. code-block:: python |
| 151 | +
|
| 152 | + >>> s = StackCode() |
| 153 | + >>> s.generate_code(t4) |
| 154 | + [('PUSH', 1), ('PUSH', 2), ('PUSH', 3), ('PUSH', 4), ('SUB',), |
| 155 | + ('MUL',), ('PUSH', 5), ('DIV',), ('ADD',)] |
| 156 | + >>> |
14 | 157 |
|
15 | 158 | ----------
|
16 | 159 | 讨论
|
17 | 160 | ----------
|
18 |
| -todo... |
| 161 | +刚开始的时候你可能会写大量的if/else语句来实现, |
| 162 | +这里观察者模式的好处就是通过 ``getattr()`` 来获取相应的方法,并利用递归来遍历所有的节点: |
| 163 | + |
| 164 | +.. code-block:: python |
| 165 | +
|
| 166 | + def binop(self, node, instruction): |
| 167 | + self.visit(node.left) |
| 168 | + self.visit(node.right) |
| 169 | + self.instructions.append((instruction,)) |
| 170 | +
|
| 171 | +还有一点需要指出的是,这种技术也是实现其他语言中switch或case语句的方式。 |
| 172 | +比如,如果你正在写一个HTTP框架,你可能会写这样一个请求分发的控制器: |
| 173 | + |
| 174 | +.. code-block:: python |
| 175 | +
|
| 176 | + class HTTPHandler: |
| 177 | + def handle(self, request): |
| 178 | + methname = 'do_' + request.request_method |
| 179 | + getattr(self, methname)(request) |
| 180 | + def do_GET(self, request): |
| 181 | + pass |
| 182 | + def do_POST(self, request): |
| 183 | + pass |
| 184 | + def do_HEAD(self, request): |
| 185 | + pass |
| 186 | +
|
| 187 | +观察者模式一个缺点就是它严重依赖递归,如果数据结构嵌套层次太深可能会有问题, |
| 188 | +有时候会超过Python的递归深度限制(参考 ``sys.getrecursionlimit()`` )。 |
| 189 | + |
| 190 | +可以参照8.22小节,利用生成器或迭代器来实现非递归遍历算法。 |
| 191 | + |
| 192 | +在跟解析和编译相关的编程中使用观察者模式是非常常见的。 |
| 193 | +Python本身的 ``ast`` 模块值的关注下,可以去看看源码。 |
| 194 | +9.24小节演示了一个利用 ``ast`` 模块来处理Python源代码的例子。 |
| 195 | + |
0 commit comments