vendredi 30 septembre 2016

Dynamic method binding with inheritance in Python

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