1

I am doing some research on the Visitor Design Pattern implementations in Python and came across an interesting version which I see a lot of potential to use in real world scenarios but it also confuse me as well.

I extracted the code below from this url:

# 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

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!"

The part which causes the confusion is print(', '.join(visitor.visit(animal) for animal in animals)) - I am aware that it would print the result dynamically based on the class assigned in the decorator but I do not grasp the idea how it works under the hood. What I tried is to debug it in the IDE and found out that after the line print(', '.join(visitor.visit(animal) for animal in animals)) the decorator(fn) inner function did not execute as I go through the debug process instead it jumped to _visitor_impl(self, arg) function but the decorator(fn) actually got executed and the debugger does not show it as I tried to comment out the part below in the mentioned function:

declaring_class = _declaring_class(fn)
_methods[(declaring_class, arg_type)] = fn

and got the following error:

"D:\Program Files\Python\python.exe" "E:/Udemy/Design Patterns/Python_test/test.py"
Traceback (most recent call last):
  File "E:/Udemy/Design Patterns/Python_test/test.py", line 53, in <module>
    print(', '.join(visitor.visit(animal) for animal in animals))
  File "E:/Udemy/Design Patterns/Python_test/test.py", line 53, in <genexpr>
    print(', '.join(visitor.visit(animal) for animal in animals))
  File "E:/Udemy/Design Patterns/Python_test/test.py", line 18, in _visitor_impl
    method = _methods[(_qualname(type(self)), type(arg))]
KeyError: ('__main__.ZooVisitor', <class '__main__.Lion'>)

Process finished with exit code 1

Also, I replicated the decorating behavior as below:

def decorator_function(original_function):
    def wrapper_function(test):
        print(test)
        return original_function()
    return wrapper_function


@decorator_function
def display():
    print('display function ran')

display('test2')

In the scipt above, the debugger did go into the wrapper_function() and executed all the step in there unlike the Visitor Design Pattern which jumped straight to _visitor_impl(self, arg)

My question is: Why does the Visitor Design Pattern script does not go into its wrapper function decorator(fn) ?

As the debugger does not show how the visitor() function process, how does the visitor(arg_type), _visitor_impl(self, arg), _qualname(obj) & _declaring_class(obj) interact with each others ?

4
  • 1
    One thing to note is that visitor() and decorator() are only executed as the ZooVisitor class is defined and never at 'runtime'. Commented Sep 4, 2022 at 13:30
  • @quamrana, Do you mean that visitor() and decorator() run at line visitor = ZooVisitor() as I debugged on this line as well but it did not jump to these functions or maybe can you elaborate further ? Commented Sep 4, 2022 at 16:03
  • 1
    No. Classes in python are 'executed' as python reads the code. One part of this is that it calls any decorators whilst defining methods of a class. I think of this like python is executing: ZooVisitor.visit = visitor(Lion)(visit), which is very similar to: ZooVisitor.visit = decorator(visit). You should try to set a break point inside def visitor(). Commented Sep 4, 2022 at 16:51
  • Thank you @quamrana for pointing me to the right direction of debugging. Actually, those mentioned methods which caused me the confusion were executed when the classes were defined and I looked into the wrong place. Commented Sep 6, 2022 at 3:40

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.