问题
I am new to python (~ a month), and I wish I had switched to it sooner (after years of perl).
Problem: I want objects to have different functionality to the same method call based on their type. The methods are assigned at runtime, based on the module loaded (which composes all objects).
Question: I wanted to know if there was a popular design pattern that I could use instead of the below, or if this already has a design pattern name (I sadly have no formal CS background, and knowing this will help my documentation)?
I have a class hierarchy (as of now, 26 of them with 3 base classes). Only the base classes have some trivial methods (eg: add_child), and each derived class only extends the base class with new data attributes (specific to the derived class), overriding methods when necessary (eg: __str__).
I am dealing with tree(s) where nodes are of different classes. Yet, the nodes should the same certain method names (stringify
, print_tree
, emit
, eval
, transform_X
, etc), thereby allowing easy/blind iterator operation. Each method may do something different, yet the methods have the same call name (like polymorphism).
I primarily wanted to grant specific abilities (methods) to nodes, based on their type. Initially, I implemented this using the Visitor Pattern. But, then realized I didn't really have to, considering I was working in Python.
In the below example, I have methods which are dynamically assigned to classes. Note, in below example the iteration/recursion method call name (print_tree
) is different from the function name (generic__print_tree
).
#astmethods.py
def generic__print_tree(self, level=1):
"""
Desc: print nodes with indentation
Target: Any tree node
"""
print("{}> {}".format('-' * level, self))
for child in self.children:
child.print_tree((level + 1))
def ASTNode__stringify(self):
"""
Desc: Return string representation of the tree under this node
Target: AST/CFG nodes
"""
text = str(self)
for child in self.children:
text += ", { " + child.stringify() + " }"
return text
Finally the main modules has this function, extend_types() which gets called during module init. The nodes are expected to do different things, within the context of this module, based on their type (not value). The methods assigned are inherited, unless overridden.
# mainModule1.py
def extend_types():
"""
Upgrade the AST node classes with neat functions for use within this module's context
"""
# same simple functions across class hierarchies
# I should just derive them both from a common base class to avoid this
ASTNode.print_tree = generic__print_tree
SimpleNode.print_tree = generic__print_tree
# ASTNode and all derived class get this method
ASTNode.stringify = ASTNode__stringify
# All AST nodes get the base method, except for Ignore and Arraysel type nodes
# traversal looks the same with child.tidy()
ASTNode.tidy = ASTNode__tidy
ASTIgnore.tidy = ASTIgnore__tidy
ASTArraySel.tidy = ASTArraySel__tidy
# All AST nodes get the base method, except for the Coverage and If type nodes
ASTNode.transform_controlFlow = ASTNode__transform_controlFlow
ASTCoverage.transform_controlFlow = ASTCoverage__transform_controlFlow
ASTIf.transform_controlFlow = ASTIf__transform_controlFlow
edit: removed distracting info, made the example for a single module context
回答1:
Problem Summary
Ignoring the irrelevant details, the problems here can be summed up as follows:
There is one base class and many derived classes. There is a certain functionality which should apply to all of the derived classes, but depends on some external switch (in the question: choice of "main module").
The idea in the question is to monkeypatch the base class depending on the switch.
Solution
Instead of that, the functionality which depends on the external switch should be separated.
In example:
# There is a base class:
class ASTNode(object):
pass
# There are many derived classes, e.g.:
class ASTVar(ASTNode):
pass
# One implementation of function stringify or another
# should be defined for all objects, depending on some external factor
def stringify1(node):
# something
def stringify2(node):
# something else
# Depending on whatever, choose one of them:
stringify = stringify1
This is now used just a little bit differently than in the original code: intead of node.stringify()
, there is now stringify(node)
. But there is nothing wrong with that.
BTW...
Maybe it would be more pleasing to the eye to use a class:
class NodeHandler1(object): def print_tree(node): # do something
def stringify(node):
# do something
...
But that is not required at all.
The Moral
Do not monkeypatch. That is always bad design.
来源:https://stackoverflow.com/questions/39799973/dynamic-method-binding-with-inheritance-in-python