I am writing an API in Python 3.9. I'll first provide a simplified example of what I'm working on and below the code will expand a bit on what I'm trying to implement from a design perspective. I just started getting into more complex design and object composition things, so it's entirely possible that some of what I'm trying to do is either not possible or not best practice - I'd be interested to know what I can improve.
In primary_module.py
:
from some_module import do_something
from secondary_module import SecondaryClass
class PrimaryClass:
def __init__(self, param1, param2):
self.attr1 = param1
self._attr2 = do_something(param2)
def primary_method(self):
sec_cls = SecondaryClass(PrimaryClass)
sec_cls.secondary_method()
In secondary_module.py
:
class SecondaryClass:
def secondary_method(pri_cls):
print(pri_cls.attr1)
print(pri_cls._attr2)
As you can see, I have one class SecondaryClass
defined in secondary_module.py
and am using that to compose a class called PrimaryClass
in primary_module.py
. From an API standpoint, I would like end users to only directly access PrimaryClass
and never let them see SecondaryClass
even though it is being used under the hood. The only time SecondaryClass
needs to be instantiated is when running PrimaryClass.primary_method()
and related objects, so there's no situation in which SecondaryClass
would run as a standalone object. However, when running this method, SecondaryClass
needs access to some of the user-specified data contained in PrimaryClass
. Keeping in mind that SecondaryClass
is (theoretically) never directly accessed by end users, what's the best way to allow SecondaryClass
to see certain attributes of PrimaryClass
? Is it acceptable to use duck typing in the definition of SecondaryClass.secondary_method()
to grant it access to certain attributes (both public and hidden) of PrimaryClass
? Or is there some better solution that I'm not thinking of?
CodePudding user response:
It's not totally clear, but I think this is what you want:
class PrimaryClass:
def __init__(self, param1, param2):
self.attr1 = param1
self._attr2 = do_something(param2)
def primary_method(self):
sec_cls = SecondaryClass(self)
sec_cls.secondary_method()
def __private_method(self):
do_something_else()
class SecondaryClass:
def __init__(self, primary):
self.primary = primary
def secondary_method():
print(self.primary.attr1)
print(self.primary._attr2)
To hide SecondaryClass
from users of primary_module
, use:
from secondary_module import SecondaryClass as _SecondaryClass
See Hide external modules when importing a module (e.g. regarding code-completion)
CodePudding user response:
It's hard to discuss this without understanding what the classes are actually doing.
That said, it's generally not a good idea to have two classes mutually depending on each other as this tends to generate brittle code: changes in either class will potentially break both classes and whoever depends on either of them.
It'd be better to pass secondary_method
what it needs explicitly (i.e. secondary_method(self._attr2)
), possibly as a function pointer (e.g. lambda: __private_method()
, lambda: self.attr1
), either through the class constructor (i.e. __init__
) or as a method parameter for secondary_method
. This way, SecondaryClass
doesn't depend on PrimaryClass
specifically and the dependencies from secondary_method
and SecondaryClass
are made explicit.
class PrimaryClass:
def __init__(self, param1, param2):
self.attr1 = param1
self._attr2 = do_something(param2)
def primary_method(self):
# E.g. 1: Pass _attr2 via constructor
sec_cls = SecondaryClass(self._attr2)
# E.g. 2 3: Pass attr1 and _attr2 as method parameters
sec_cls.secondary_method(
lambda: self.attr1,
lambda: __private_method(),
)
def __private_method(self):
do_something_else()
class SecondaryClass:
def __init__(self, attr2):
self.attr2 = attr2
def secondary_method(get_attr1, private_callback):
print(self.attr2) # <- _attr2, received from constructor
print(get_attr1()) # <- Getting attr1 at execution time from lambda
private_callback() # <- Calling __private_method