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 ?
visitor()anddecorator()are only executed as theZooVisitorclass is defined and never at 'runtime'.visitor()anddecorator()run at linevisitor = ZooVisitor()as I debugged on this line as well but it did not jump to these functions or maybe can you elaborate further ?ZooVisitor.visit = visitor(Lion)(visit), which is very similar to:ZooVisitor.visit = decorator(visit). You should try to set a break point insidedef visitor().