Visitor pattern in Python

[AST] (http://ja.wikipedia.org/wiki/%E6%8A%BD%E8%B1%A1%E6%A7%8B%E6%96%87%E6%9C%A8) is being investigated [Visitor pattern] (http://ja.wikipedia.org/wiki/Visitor_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3) came out. What made you happy with the Visitor pattern? I didn't know the basics, so I reviewed it: sweat:

Wikipedia has a Java code example, so I rewrote it in Python3. At first, I ported the Japanese code while looking at the example, and if I thought that it was a half-finished sample, the English Visitor pattern ) Has been modified to a more concise code example. It seems better to look at this page in English.

Python doesn't have an interface, so I'm using the @abstractmethod decorator from the abc module. As an aside, the * abc * module only applies to instances of classes, but * zope.interface * is great for classes, objects, modules, etc.

Quiet talk break. Let's look at the pattern while looking at the code.

3.4


# -*- coding: utf-8 -*-
from abc import ABCMeta, abstractmethod

class ICarElementVisitor(metaclass=ABCMeta):
    """
    Interface like in Python
    """
    @abstractmethod
    def visit_Wheel(self, wheel): pass

    @abstractmethod
    def visit_Engine(self, engine): pass

    @abstractmethod
    def visit_Body(self, body): pass

    @abstractmethod
    def visit_Car(self, car): pass

class ICarElement(metaclass=ABCMeta):
    """
    Interface like in Python
    """
    @abstractmethod
    def accept(self, visitor): pass

class Wheel(ICarElement):
    def __init__(self, name):
        self.name = name

    def accept(self, visitor):
        visitor.visit_Wheel(self)

class Engine(ICarElement):
    def accept(self, visitor):
        visitor.visit_Engine(self)

class Body(ICarElement):
    def accept(self, visitor):
        visitor.visit_Body(self)

class Car(ICarElement):

    def __init__(self):
        self.elements = [
            Wheel('front left'), Wheel('front right'),
            Wheel('back left'), Wheel('back right'),
            Body(), Engine(),
        ]

    def accept(self, visitor):
        for elem in self.elements:
            elem.accept(visitor)
        visitor.visit_Car(self)

class PrintVisitor(ICarElementVisitor):
    def visit_Wheel(self, wheel):
        print('Visiting {} wheel'.format(wheel.name))

    def visit_Engine(self, engine):
        print('Visiting engine')

    def visit_Body(self, body):
        print('Visiting body')

    def visit_Car(self, car):
        print('Visiting car')

class DoVisitor(ICarElementVisitor):
    def visit_Wheel(self, wheel):
        print('Kicking my {} wheel'.format(wheel.name))

    def visit_Engine(self, engine):
        print('Starting my engine')

    def visit_Body(self, body):
        print('Moving my body')

    def visit_Car(self, car):
        print('Starting my car')

def main():
    """
    >>> main()
    Visiting front left wheel
    Visiting front right wheel
    Visiting back left wheel
    Visiting back right wheel
    Visiting body
    Visiting engine
    Visiting car
    --------------------------------
    Kicking my front left wheel
    Kicking my front right wheel
    Kicking my back left wheel
    Kicking my back right wheel
    Moving my body
    Starting my engine
    Starting my car
    """
    car = Car()
    car.accept(PrintVisitor())
    print('-' * 32)
    car.accept(DoVisitor())

If you port the Java code as it is, it looks like this, but since it is Python, let's loosen it a little more.

--- visitor.py	2015-02-17 18:43:53.000000000 +0900
+++ visitor-generic.py	2015-02-17 18:46:24.000000000 +0900
@@ -6,16 +6,8 @@
     Interface like in Python
     """
     @abstractmethod
-    def visit_Wheel(self, wheel): pass
-
-    @abstractmethod
-    def visit_Engine(self, engine): pass
-
-    @abstractmethod
-    def visit_Body(self, body): pass
-
-    @abstractmethod
-    def visit_Car(self, car): pass
+    def visit(self, obj):
+        getattr(self, 'visit_' + obj.__class__.__name__)(obj)
 
 class ICarElement(metaclass=ABCMeta):
     """
@@ -29,15 +21,15 @@
         self.name = name
 
     def accept(self, visitor):
-        visitor.visit_Wheel(self)
+        visitor.visit(self)
 
 class Engine(ICarElement):
     def accept(self, visitor):
-        visitor.visit_Engine(self)
+        visitor.visit(self)
 
 class Body(ICarElement):
     def accept(self, visitor):
-        visitor.visit_Body(self)
+        visitor.visit(self)
 
 class Car(ICarElement):
 
@@ -51,9 +43,12 @@
     def accept(self, visitor):
         for elem in self.elements:
             elem.accept(visitor)
-        visitor.visit_Car(self)
+        visitor.visit(self)
 
 class PrintVisitor(ICarElementVisitor):
+    def visit(self, obj):
+        getattr(self, 'visit_' + obj.__class__.__name__)(obj)
+
     def visit_Wheel(self, wheel):
         print('Visiting {} wheel'.format(wheel.name))
 
@@ -67,6 +62,9 @@
         print('Visiting car')
 
 class DoVisitor(ICarElementVisitor):
+    def visit(self, obj):
+        super().visit(obj)
+

To briefly explain the changes, we use reflection to define a default implementation for something interface-like.

3.4


class ICarElementVisitor(metaclass=ABCMeta):
    """
    Interface like in Python
    """
    @abstractmethod
    def visit(self, obj):
        getattr(self, 'visit_' + obj.__class__.__name__)(obj)

Then, the class that receives the visitor can be unified to call visitor.visit (self), which is a little cleaner.

3.4


class Engine(ICarElement):
    def accept(self, visitor):
        visitor.visit(self)

The Visitor subclass may directly define an implementation of visit (), such as PrintVisitor, or it may use the default implementation of an abstract class (interface), such as DoVisitor.

3.4


class PrintVisitor(ICarElementVisitor):
    def visit(self, obj):
        getattr(self, 'visit_' + obj.__class__.__name__)(obj)
    ...

class DoVisitor(ICarElementVisitor):
    def visit(self, obj):
        super().visit(obj)
    ...

The Visitor pattern passes the Visitor object to the ʻaccept ()method and calls thevisitor.visit (self) method inside it ([Double Dispatch](http://en.wikipedia.org/wiki/Double_dispatch)). ). This example also calls the ʻelem.accept (visitor) method of each element object inside car.accept (visitor).

One of the benefits is that you can operate across multiple element objects (* traverse *) so that you don't have to change the code or data structure of an existing object when you add a new operation [Separation of]. It leads to concerns (http://en.wikipedia.org/wiki/Separation_of_concerns).

For example, if you think about adding an operation called braking,

+class Brake(ICarElement):
+    def accept(self, visitor):
+        visitor.visit(self)
+
 class Car(ICarElement):
 
     def __init__(self):
         self.elements = [
             Wheel('front left'), Wheel('front right'),
             Wheel('back left'), Wheel('back right'),
-            Body(), Engine(),
+            Body(), Engine(), Brake(),
         ]
@@ -55,6 +59,9 @@
     def visit_Engine(self, engine):
         print('Visiting engine')
 
+    def visit_Brake(self, engine):
+        print('Visiting brake')
+        

That's all you need to do.

In addition, the visitor object is described as being more powerful than the * polymorphic * method because it can also have states.

In this example, we don't manage the state in particular, but it's easy to record the order in which they were called, or flag them when an operation is performed.

After writing it and re-examining it, [Comparison of visitor pattern and duck typing] According to (http://d.hatena.ne.jp/podhmo/20101127/1290845198), [Expert Python Programming](http://ascii.asciimw.jp/books/books/detail/978-4-04- Was it listed in 868629-7.shtml)? It was many years ago, and I remember attending a book club at that time and talking about it, but I didn't remember the code at all.

Recommended Posts

Visitor pattern in Python
Singleton pattern in Python
Learn the design pattern "Visitor" in Python
Implement the Singleton pattern in Python
Quadtree in Python --2
Python in optimization
CURL in python
Metaprogramming in Python
Python 3.3 in Anaconda
Geocoding in python
SendKeys in Python
Meta-analysis in Python
Unittest in python
Epoch in Python
Discord in Python
Sudoku in Python
DCI in Python
quicksort in python
nCr in python
N-Gram in Python
Programming in python
Plink in Python
Constant in python
Lifegame in Python.
FizzBuzz in Python
Sqlite in python
StepAIC in Python
N-gram in python
LINE-Bot [0] in Python
Csv in python
Disassemble in Python
Reflection in Python
Constant in python
nCr in Python.
format in python
Scons in Python3
Puyo Puyo in python
python in virtualenv
PPAP in Python
Quad-tree in Python
Reflection in Python
Chemistry in Python
Hashable in python
DirectLiNGAM in Python
LiNGAM in Python
Flatten in python
flatten in python
Learn the design pattern "Prototype" in Python
Learn the design pattern "Builder" in Python
Learn the design pattern "Observer" in Python
Learn the design pattern "Proxy" in Python
Learn the design pattern "Command" in Python
Learn the design pattern "Bridge" in Python
Learn the design pattern "Mediator" in Python
Learn the design pattern "Decorator" in Python
Learn the design pattern "Iterator" in Python
Learn the design pattern "Strategy" in Python
Learn the design pattern "Composite" in Python
Learn the design pattern "State" in Python
Learn the design pattern "Adapter" in Python
Sorted list in Python