[Python] how to call a def in another class that in turn calls a def of the caller class

How can I sort out code from a class to another file?

For example I have a large class called “Writer”. it has dozens of defs and they call each other. I want to move some defs to another .py file to keep the overview.

My attempt was to take some def from class “Writer” to a new file, put them there into a class called e.g. “ESwriter”.
The point is that the defs in calss ESwriter in turn calls some other defs in class Writer. This compiles but when executed the other defs in calss Writer do not return anything.
An example:

class ESwriter:
    def init(self, solver, directory):
        self.solver = solver
        self.directory = directory
        self.write = writer.Writer(self.solver, self.directory)
    def _handleElectrostaticConstants(self):
        self.write._constant(
            "Permittivity Of Vacuum",
            self.write._convert(self.write.constsdef["PermittivityOfVacuum"], "T^4*I^2/(L^3*M)")
        )

in the Writer class, make the initial call this way:

ESW = test.ESwriter(self.solver, self.directory)
ESW._handleElectrostaticConstants()

How is this done right?

Sounds like you have a circular dependency, but I believe your code could still run.
If you either could share the working code (before change), a minimal running example (after the change), or both. That would be good to better know what gets called when, and what is supposed to happen.

Something like

# test.py
import ...

class ESwriter:
    ...



# writer.py
import ...

class Writer:
    def __init__(self, solver, directory):
        ...
        
    def _convert(self, ...):
        ...
        return ...
        
    def _constant(self, ...):
        ...
    
    constdef = {"PermittivityOfVacuum": ...}

splitting a class in 2 and use them as they were not split?
inheritance and super? (regardless if same file or not)…

#from . import A

class A():
    def __init__(self):
        self.a = 1
        
    def mod_a(self, new):
        self.a = new
        
    def printa(self):
        print(self.a)
        
class B(A):
    def __init__(self):
        super().__init__()
        
    def new_a_value(self, a):
        self.mod_a(a)



b = B()
b.printa()
b.new_a_value(2)
b.printa()

if args, super().init(*args, **kwargs) will pass all of it to instance A.

Can’t you just leave out init in class B?

Thanks.

The file that should be split is attached:
writer.py
From this file the defs named “*ElectrostaticSolver” of the class “Writer” should be sorted out. Here is my attempt to sort them out to a new file:
electrostatic_writer.py

Do I understand correctly that you have added something like this in writer.py?

    def _handleElectrostatic(self):
        ...
        if activeIn:
            ESW = test.ESwriter(self.solver, self.directory)
            ESW._handleElectrostaticConstants()

If so. You’re creating a new instance of Writer when you create an instance of ESwriter. That looks suspicious. If you just want to split the code into separate files this should be enough:

# writer.py
    def _handleElectrostatic(self):
        ...
        if activeIn:
            ESW = test.ESwriter(this, self.solver)
            ESW._handleElectrostaticConstants()
            ...



# electrostatic_writer.py
class ESwriter:
    def __init__(self, writer, solver):
        self.writer = writer
        self.solver = solver
    ...

Yes and this is why I am stuck. I only want to split the too long writer.py but get a circular dependency.

many thanks.
However this still elads to a circular dependency.

I have now this:

# writer.py
from .equations import electrostatic_writer as ES_writer
class Writer(object):
    def __init__(self, solver, directory, testmode=False):
        self.analysis = solver.getParentGroup()
        ...
    def _handleElectrostatic(self):
        ...
        if activeIn:
            ESW = ES_writer.ESwriter(this, self.solver)
            ESW._handleElectrostaticConstants()
            ...



# electrostatic_writer.py
class ESwriter:
    def __init__(self, writer, solver):
        self.writer = writer
        self.solver = solver
    def _getElectrostaticSolver(self, equation):
        # check if we need to update the equation
        self._updateElectrostaticSolver(equation)
        ...

This gives me this error:

File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\writer.py", line 98, in write_solver_input
      self._handleElectrostatic()
    File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\writer.py", line 857, in _handleElectrostatic
      ESW = ES_writer.ESwriter(this, self.solver)
  NameError: name 'this' is not defined

When I replace the unknown “this” by “self”

I get however, this error:

05:59:31    File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\writer.py", line 857, in _handleElectrostatic
05:59:31      ESW = ES_writer.ESwriter(self, self.solver)
05:59:31    File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\equations\electrostatic_writer.py", line 41, in __init__
05:59:31      self.write = writer.Writer(self.solver, self.directory)
05:59:31    File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\writer.py", line 79, in __init__
05:59:31      self.analysis = solver.getParentGroup()
05:59:31  AttributeError: 'Writer' object has no attribute 'getParentGroup'

What I need is that the def “_getElectrostaticSolver” from the ESWriter calls uses the defs in the already loaded Writer class and not to re-initialize the Writer class.

Oops, sorry. Should be self.


Looks like ESWriter.init does not look like this:

    def __init__(self, writer, solver):
        self.writer = writer
        self.solver = solver

But it does. See attached:
electrostatic_writer.py

I’ll give the code a look in a minute…

Side note: it appears you are making a public function that you prefix with an underscore. In Python we do not do that, the underscore is used to indicate that a function is “private”, that is, that your IDE should not list it as a possible completion for outside code to call. It’s just a convention, of course, Python itself doesn’t have the idea of private functions. Nevertheless, it appears from the code blocks in this discussion that you should lose the underscore in the method name.

Two things jump out at me: first, you do not need to import writer – since you are passing it into the class, there is no need for the line that also imports its definition. Python doesn’t care – as long as the writer object provides the needed functions at call-time, you are good to go.

Second, you appear to be referencing a non-existent object (write):

def _getElectrostaticSolver(self, equation):
        # check if we need to update the equation
        self._updateElectrostaticSolver(equation)
        # output the equation parameters
        s = self.write._createLinearSolver(equation)

As far as I can see, there is no “self.write” – maybe you mean “self.writer”?

Many thanks! I fixed this and now it works.

I made a PR to make the first step to refactor the writer.py beast:

I hereby also followed the naming convention that defs now addressed by another class, don’t get the underscore. I hope I did it right.

Edit: Saw, after I posted, that you got it to work so you can ignore this post.

uwestoehr
The stack trace cannot look like this:

05:59:31    File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\writer.py", line 857, in _handleElectrostatic
05:59:31      ESW = ES_writer.ESwriter(self, self.solver)
05:59:31    File "D:\FreeCAD-build\Mod\Fem\femsolver\elmer\equations\electrostatic_writer.py", line 41, in __init__
05:59:31      self.write = writer.Writer(self.solver, self.directory)

If ESwriter only have one definition of init, that looks like the following, in electrostatic_writer.py:

class ESwriter:

    def __init__(self, writer, solver):
        self.writer = writer
        self.solver = solver

According to the stack trace ESwriter.init in D:\FreeCAD-build\Mod\Fem\femsolver\elmer\equations\electrostatic_writer.py, line 41, looks like this:

self.write = writer.Writer(self.solver, self.directory)

Have this open for a long time.
In case it may be useful again, I guess you could use dynamic subclassing in Python. Here is a very simple example to demonstrate :

class Writer:

	def __init__(self, es=False):
		self.testme()
		App.Console.PrintMessage("Testing if needs dynamic subclassing\n")
		if es:
			self.__class__ = ESWriter
			self.specificInit()
		self.testme()
		App.Console.PrintMessage("---------------\n")

	def testme(self):
		App.Console.PrintMessage("I'm a Writer\n")

class ESWriter(Writer):

	def __init__(self):
		super.__init__()
		specificInit()

	def specificInit(self):
		# do specific init actions here
		pass

	def testme(self):
		App.Console.PrintMessage("I'm a ESWriter\n")

Writer()

Writer(True)

Many thanks!

However, in the meantime I was able to refactor the file, thanks to the help of @agren and @chennes. This was the first step:
https://github.com/FreeCAD/FreeCAD/pull/8362/commits/239165305d9946d2c997efea040eadf5acb6fe7a
then I refactored is step by step the same way.