I am new to python (~ a month), and I wish I had switched to it sooner (after years of perl). 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__).
Assume the code for the classes can not be edited, but the classes can be extended with methods during runtime (like Visitor Pattern). Therefore the module that has all the class definitions remains clean and minimal. There exists another module that contains various functions which can be bound to the classes during runtime.
I am dealing with tree(s) where nodes are of different classes. Yet, the nodes have the same method names, thereby allowing easy/blind iterator operation. Each method may do something different (like polymorphism). These methods are dynamically assigned to classes, based on what module gets loaded. The method is inherited, unless overridden.
# asttypes.py
class ASTNode(object):
def __init__(self, level, linenum, tag):
self.children = []
self.level = level
self.linenum = linenum
self.tag = tag
self.nodetype = self.__class__.__name__
def add_child(self, node):
self.children.append(node)
def __str__(self):
return "[{}] {}".format(self.tag, self.nodetype)
class ASTVar(ASTNode):
def __init__(self, level, linenum, tag, name, vartype, width):
ASTNode.__init__(self, level, linenum, tag)
self.name = name
self.vartype = vartype
self.width = width
def __str__(self):
return "[{}] {}: {}, {} of width {}".format(self.tag, self.nodetype, self.name, self.vartype, self.width)
I primarily wanted to grant specific abilities (methods) to each of the classes, yet, be able to call them using the same name. Note, in below example the iteration/recursion method call name is different from the function name.
Initially, I implemented this using the Visitor Pattern. But, then realized I didn't really have to, considering I was working in Python.
#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 one of the main modules has this function, extend_types() which gets called during module init. There are multiple functionally exclusive main modules, and each has its own extend_types(), because the nodes are expected to do different things, within the context of this module. The methods are inherited, unless overridden.
The main modules are not expected to be imported at the same time. If these modules were to be used at the same time, then the hasattr() check would need to be removed, and the modules' extend_types() would be first called internally within the module functions.
You can see how this extends to having a different eval() (instrument the predicate per CFG block) or emitC() (print C code equivalent) method per class.
# doSomethingCoolWithVerilatorASTDumps.py
def extend_types():
"""
Upgrade the AST node classes with neat functions
"""
if not hasattr(ASTNode, 'print_tree'):
ASTNode.print_tree = generic__print_tree
if not hasattr(ASTNode, 'transform_controlFlow'):
ASTNode.transform_controlFlow = ASTNode__transform_controlFlow
if not hasattr(ASTNode, 'stringify'):
ASTNode.stringify = ASTNode__stringify
if not hasattr(ASTNode, 'tidy'):
ASTNode.tidy = ASTNode__tidy
if not hasattr(SimpleNode, 'print_tree'):
SimpleNode.print_tree = generic__print_tree
Aucun commentaire:
Enregistrer un commentaire