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.