I have a Game
that references classes HouseRules
, Card
, and Player
. I'd like to add functionality to each class.
I've made a subclass called Game
and subclasses called HouseRules
, Card
, and Player
that have new methods and attributes in addition to those of their corresponding parents. I'd like the unchanged methods to still function as expected, except that they refer to the updated forms of each class. Below is my minimal reproducible example with just one class (subclass) that depends on one class (subclass).
I've created a class Foo
that takes in its initialization an object of class Obj
. If no object is given, it's set to a default Obj()
.
'src.py'
class Obj:
def __init__(self, a=1, b=1):
self.a = a
self.b = b
class Foo:
def __init__(self, obj=None):
self.obj=Obj() if obj is None else obj
...
I'd like to build on Foo
's functionality in a new project, so I've made a new file and subclassed src.Foo
.
This new Foo
's functionalities will need a slightly more complicated Obj
that has an attribute z
, so I made a subclass of src.Obj
.
'main.py'
import src
class Obj(src.Obj):
def __init__(self, z=0, **kwargs):
self.z = z
super().__init__(**kwargs)
class Foo(src.Foo):
def __init__(self, obj=None):
#self.obj=Obj() if obj is None else obj
super().__init__(obj)
Originally I didn't include the commented line. I thought that if obj
was passed as None
, super().__init__(obj)
would turn obj
into the new improved Obj()
.
But it became the old Obj()
and threw an error when I tried to access its nonexistent attribute z
.
So, I added the line and this fixes it.
Another idea is to replace
'src.py'
class Foo:
def __init__(self, obj=None):
self.obj=Obj() if obj is None else obj
with
'src.py'
class Foo:
def __init__(self, obj=None):
self.obj=self.default_obj if obj is None else obj
@property
def default_obj(self): return Obj()
Then copy the getter and paste it in Foo
in 'main.py', where it will return the Obj()
from 'main.py'.
A third idea is to make Obj
a nested class and set self.obj
to self.Obj()
.
'src.py'
class Foo:
class Obj:
def __init__(self, a=1, b=1):
...
def __init__(self, obj=None):
self.obj=self.Obj() if obj is None else obj
...
'main.py'
import src
class Foo(src.Foo):
class Obj(src.Foo.Obj):
def __init__(self, z=0, **kwargs):
...
def __init__(self, obj=None):
super().__init__(obj)
I've heard nested classes are generally to be avoided. Also, I noticed I couldn't use super().Obj
, I had to type src.Foo.Obj
, which means if I change the name of the superclass or something, I'd have to be sure to change it in two places.
But my question is: in the interest of avoiding repeating the obj
-assigning line (or alternatively repeating the getter or the class inheritance, or having src.py import main), is there any way to call the parent class's initialization that will turn obj
into the Obj()
in 'main.py' instead of 'src.py'?
Although it would only be repetition of one simple line (or getter) in this minimal case, I actually have three updated subclasses. To use these techniques I've considered would mean copying all superclass methods that create instances of these subclasses verbatim into the subclass so that they refer to the new subclasses, which diminishes the benefits of using inheritance.
If there's a better practice for achieving what I'm attempting than subclassing each class of a project, feel free to let me know.
First thigns first - don't do nested classes. :-)
So - a simple way to do that, is to have the base-associated classes you rely on as class attributes in the base Foo
, (or Game
) - and retrieve that attribute to instantiate each class. Then, in the subclass of Foo
(Game
), simply override those attributes.
No need to mess around with properties or whatever:
# src.py
class Obj:
...
class Foo:
# This binds this class.Obj to the "Obj" visible in
# the global scope
Obj = Obj
def __init__(self, obj=None):
self.obj=self.Obj() if obj is None else obj
...
# other.py
import src
class Obj(src.Obj):
# add extra stuff and overrides as desired!
...
class Foo(src.Foo):
# Binds the class Obj attribute to the Obj visible here!
Obj = Obj
# Just with that, if nothing changes in __init__
# you don't even need to redeclare it and call super()...:
# the original Foo.__init__ will take care of
# instantianting the derived Obj visible here!