Visitor pattern in python

You could implement this in Python, but there's really no need to. Python is a dynamic, interpreted language, which means that type information is easily available at runtime.

So your above example could be as simple as

class C1(object):
    pass

class C2(object):
    pass

l = [C1(), C2()]
if __name__=="__main__":
    for element in l:
        print type(element)

which will yield:

<class '__main__.C1'>
<class '__main__.C2'>

First of all

  • what is object oriented programming? it is dynamic dispatch on a single argument (the implicit this in java & C++)
  • what does the visitor pattern do: It tries to emulate double dispatch on those languages, so it uses two classes as a work-around.

You could do it the same way in python, but you could also implement double dispatch with a decorator. (Lisp's CLOS uses a vaguely similar approach)

class A: pass
class B: pass

class visitor:
    def __init__(self, f):
        self.f = f
        self.cases = {}

    def case(self, type1, type2):
        def call(fun):
            self.cases[(type1, type2)] = fun
        return call


    def __call__(self, arg1, arg2):
        fun = self.cases[type(arg1), type(arg2)]
        return fun(arg1, arg2)

@visitor
def f(x, y): pass


@f.case(A, int)
def fun1(a, b):
    print("called with A and int")


@f.case(B, str)
def fun2(a, b):
    print("called with B and string")



f(A(), 5)
f(B(), "hello")

The visitor pattern can be implemented in Python, I use it to implement a clean interface between my data and presentation layer. The data layer can determine the ordering of the data. and the presentation layer simply prints/formats it :

In my data module I have :

 class visited(object):
     ....
     def accept(self, visitor):
         visitor.visit(self)
         for child in self.children():
             child.accept(visitor)

 class typeA(visited):
    ....

All of my data classes inherit from this visited class, and the visited class also exposes some simple functions for basic data all my objects need e.g. name, parent, etc, and methods for managing the child list - which is exposed by the children() method used above. each of the sub class will build their own data, have their own properties and maybe even their own child class - which get added to the children list maintain by the visited super class.

My visitor class is like this :

class visitor(object):
      def __init__(self, obj_id):
          data_obj = _find_data_instance( obj_id )
          data_obj.accept(self)

      def visit( self, data_obj):
          if isinstance(data_obj, typeA):
               self.visit_typeA( dataobj)

      def visit_typeA(self, dataobj):
          """Formats the data for typeA"""
          ...

the _find_data_instance is some code that builds or finds a instance of one of my data instances. In my case all of my data classes have a constructor which takes a objectId and return, and the visitor object knows what data class to use.


You can use decorators to get what you want. Copying an example from this blog:

class Lion: pass
class Tiger: pass
class Bear: pass

class ZooVisitor:
    @visitor(Lion)
    def visit(self, animal):
        return "Lions"

    @visitor(Tiger)
    def visit(self, animal):
        return "tigers"

    @visitor(Bear)
    def visit(self, animal):
        return "and bears, oh my!"

animals = [Lion(), Tiger(), Bear()]
visitor = ZooVisitor()
print(', '.join(visitor.visit(animal) for animal in animals))
# Prints "Lions, tigers, and bears, oh my!"

and the code for the @visitor decorator (in case the link goes dead):

# A couple helper functions first

def _qualname(obj):
    """Get the fully-qualified name of an object (including module)."""
    return obj.__module__ + '.' + obj.__qualname__

def _declaring_class(obj):
    """Get the name of the class that declared an object."""
    name = _qualname(obj)
    return name[:name.rfind('.')]

# Stores the actual visitor methods
_methods = {}

# Delegating visitor implementation
def _visitor_impl(self, arg):
    """Actual visitor method implementation."""
    method = _methods[(_qualname(type(self)), type(arg))]
    return method(self, arg)

# The actual @visitor decorator
def visitor(arg_type):
    """Decorator that creates a visitor method."""

    def decorator(fn):
        declaring_class = _declaring_class(fn)
        _methods[(declaring_class, arg_type)] = fn

        # Replace all decorated methods with _visitor_impl
        return _visitor_impl

    return decorator

Related blog (first one already seems to be down): https://chris-lamb.co.uk/posts/visitor-pattern-in-python

EDIT:

obj.__qualname__ isn't available until Python 3.3, so we have to use a hack for lower versions:-

def _qualname(obj):
    """Get the fully-qualified name of an object (including module)."""

    if hasattr(obj, '__qualname__'):
        qualname = obj.__qualname__
    else:
        qualname = str(obj).split(' ')[1]

    return obj.__module__ + '.' + qualname

Unfortunately the above solution doesn't work for python versions below 3.3, as methods are still regular functions when passed to a decorator. You can try using both a class - and method decorator, see Can a Python decorator of an instance method access the class?.

Tags:

Python

Visitor