Home > Back-end >  `python`: put class method, that returns a class instance, into seperate file
`python`: put class method, that returns a class instance, into seperate file

Time:11-09

I have a large class definition, and for clarity would like to seperate out the arithmatic dunder methods in a seperate file. These methods, however, need to be able to return a new instance of the class, which causes a cyclical import, a name error, or an ugly unmaintainability.

My motivation for wanting to do this is at the end.

Here is a miminal working example. Put all in one file...

#classa.py
class A:
    def __init__(self, val):
        self.val = val
    def __add__(self, other):
        return A(self.val   other.val)

...it works:

from .classa import A
a1 = A(30)
a2 = A(21)
(a1   a2).val # 51

Now, trying to put the __add__ method into a seperate file.

What does not work:

Attempt 1

#classa.py

from .classa_add import Aarithmatic

class A(Aarithmatic):
    def __init__(self, val):
        self.val = val


#classa_add.py

from .classa import A

class Aarithmatic:
    def __add__(self, other):
        return A(self.val   other.val)

Here we understandably get an error due to cyclic imports:

from .classa import A  # <-- ImportError
a1 = A(30)
a2 = A(21)
(a1   a2).val

Attempt 2

Just in case people point me toward this answer :)

#classa.py

from .classa_add import Aarithmatic

class A(Aarithmatic):
    def __init__(self, val):
        self.val = val


#classa_add.py

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING: 
    from .classa import A

class Aarithmatic:
    def __add__(self, other):
        return A(self.val   other.val)

Here we have a nameerror when trying to add the instances, as A is not known. Which makes sense, the 'solution' used here is for when A is used in annotations only:

from .classa import A
a1 = A(30)
a2 = A(21)
(a1   a2).val # <-- NameError: name 'A' is not defined # (in __add__)

Attempt 3

I could do it like this...

#classa.py

class A:
    def __init__(self, val):
        self.val = val

#classa_add.py

from .classa import A

def _a_add(self, other):
    return A(self.val   other.val)

A.__add__ = _a_add

# Or as oneliner: 
A.__add__ = lambda self, other: A(self.val   other.val)

but this is first of all quite ugly and unreadable, and second of all I have to import classa_add just to add the addition functionality, which is confusing:

from .classa import A
import .classa_add  # <- unclear to reader, why this needs to be here
a1 = A(30)
a2 = A(21)
(a1 a2).val # 51

If this were a package, I could add an __init__.py file to do this, but it's still ugly, and I'm sure there's a better solution.

Does anyone know, what that better solution is?

Many thanks!

I'm using python 3.8, btw.


Edit

Reasoning. Why do I even want to do this?

I have two classes (A and B) that are similar and each have quite a lot of methods. Think along the lines of pandas DataFrame and Series classes, just a bit smaller and not so similar that it is possible to have them inherit from a common parent. Currently, each class has a file in which it is defined (classa.py and classb.py).

Now, These files are firstly too long, and secondly, arithmatic on the instances is very similar.

This makes me want to create a seperate file that defines what it means to do a1 a2, b1 b2, a1 * a2, b1 * b2, but also a b and a * b.

I hope this makes it clearer why I want to do this, and I hope you agree it is more maintainable to split the class definitions up this way.

CodePudding user response:

Your Aarithmatic class can get a reference to A when its methods are called, via the self argument. It doesn't need to import A at all. If __add__ is being called on an A instance, then self.__class__ (or type(self)) should give us A.

So you could use:

def __add__(self, other):
    return self.__class__(self.val   other.val)

But I'd also like to challenge the premise of the question, that it's a good idea to split these classes up (and especially to put them in separate files). If you find that you're writing very tightly integrated code, that code should all be located together. Inheritance and modules are very nice tools, but if you find they are getting in your way, it may be a sign that they're not the right tool for your current problem.

  • Related