1

So let's consider the following classes. I have no control over classes A, B, and C and can't change the implementation.

class A(object):
   def __new__(cls, root=None):
      if root == "Some conditions":
         return super(A, cls).__init__(B)
      else:
         return super(A, cls).__init__(C)
   
   def __init__(self) -> None:
      local_variable = self._join(self.root, 'x')  # Need to modify this string to make it work with my problem. I believe this should be overridden in a child class.
      self.x = self.foo(local_variable)
   
   
class B(A):
   def __init__(self, root='local path to the downloaded file'):
      self.root = root
      self._join = os.path.join
      super(self.__class__, self).__init__()


class C(A):
   def __init__(self, root='address of the remote server to download the file'):
      self.root = root
      super(self.__class__, self).__init__()

What I'm trying to do is overriding the class A's __init__ method to change the local_variable (x is hard coded and needs to be modified) and consequently changing the self.x.

What I have tried to do is:

class D(A):
   def __new__(cls, root=None):
      return super().__new__(self, root)

   def __init__(self):
      super(D, self).__init__()
         # Not sure how to proceed and change the local_variable and self.x

I was reading this answer and was wondering if class A __new__ method will make the overriding of the __init__ method more complicated or not?

Note: I simplified the classes to make it easier to understand the problem.

Rick
  • 13
  • 3

1 Answers1

0

You can create a wrapper function that parses the source code of a given function as AST, walks through the AST nodes to replace the value of a string node (of type ast.Str) where the original value matches 'x' and recompiles the AST back to a function object:

import ast
import inspect
from textwrap import dedent

def replace_str(target, replacement):
    def wrapper(func):
        tree = ast.parse(dedent(inspect.getsource(func)))
        for node in ast.walk(tree):
            if isinstance(node, ast.Str) and node.s == target:
                node.s = replacement
        ast.fix_missing_locations(tree)
        scope = {}
        exec(compile(tree, inspect.getfile(func), "exec"), func.__globals__, scope)
        return scope[func.__name__]
    return wrapper

Then apply this wrapper on A.__init__ (simplified for illustration purpose):

class A:
    def __init__(self):
        print('x')

A.__init__ = replace_str('x', 'y')(A.__init__)
A()

This outputs:

y

Demo: https://replit.com/@blhsing/IvoryWhisperedOpendoc

blhsing
  • 91,368
  • 6
  • 71
  • 106
  • Thanks you for the suggestion. I will give it a try. I'm still wondering if this problem is solvable through inheritance and method overriding? – Rick Mar 29 '23 at 14:05