Dynamic method binding with inheritance in Python

醉酒当歌 提交于 2019-12-12 03:45:03

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!