Reputation: 535
I have 2 classes with some functions:
class A:
def __init__(self, one=1, two=2):
self.one = one
self.two = two
def do_smt(self):
...
class B:
def __init__(self, value="test"):
self.value = value
def do_smt(self):
...
I have a third class that has to use the 2 classes is doing this.
class C:
def __init__(self, one=1, two=2, value="test"):
self.A = A(one, two)
self.B = B(value)
def do_smt(self):
...
Now I do this: new_class = C()
But what if the default value of class A or B
changes, then I also need to change that in class C
. Is there a way to write class C
in a way that it knows which arguments are the default ones? It need to handle no arguments but also arguments that other classes expect.
Upvotes: 5
Views: 1190
Reputation: 8904
I think the best (and pythonic) way to write such classes is always using None as default for any optional argument in any class. In class A and B you then check whether the respective argument is None and if so, replace by your real default value. That way the default for each attribute is defined in only one place and class C doesn't need to know the default value.
Upvotes: 0
Reputation: 12204
I am not sure if this fits exactly what you want, but basically you can let C decide what to give to A, B and let A, B decide what to use, using **kwds
method parameters in A and B.
One of the differences, with the sample class C2, is that, if C has a different default value it overrides A, B.
There is also another alternative, under C3, where you use a guard value (not using None to allow that to be a default) to only pass on arguments that were given to C3.
class A:
def __init__(self, one=1, two=2, **kwds):
self.one = one
self.two = two
def do_smt(self):
pass
class B:
def __init__(self, value="test", **kwds):
self.value = value
class C:
def __init__(self, one=1, two=2, value="test"):
self.A = A(one, two)
self.B = B(value)
class C2:
""" your default values override those of A, B"""
def __init__(self, one=1, two=2, value="test"):
locals_ = locals()
locals_.pop("self")
self.A = A(**locals_)
self.B = B(**locals_)
undefined = NotImplemented
class C3:
""" your default values dont affect A and Bs"""
def __init__(self, one=undefined, two=undefined, value="test"):
locals_ = {k:v for k,v in locals().items() if k != "self" and v is not undefined}
self.A = A(**locals_)
self.B = B(**locals_)
#can still use it locally
self.one = one if one is not undefined else 11
self.two = two if two is not undefined else 22
c= C()
print("c.A.one:", c.A.one)
print("c.B.value:", c.B.value)
c2= C2()
print("c2.A.one:", c2.A.one)
print("c2.B.value:", c2.B.value)
c3= C3()
print("c3.A.one:", c3.A.one)
print("c3.one:", c3.one)
print("c3.B.value:", c3.B.value)
c.A.one: 1
c.B.value: test
c2.A.one: 1
c2.B.value: test
c3.A.one: 1
c3.one: 11
c3.B.value: test
You could even have a variant of C that uses **kwds
itself and pass those on to A, B in case they find value in it.
class C4:
""" your default values dont affect A and Bs
and you can pass in anything.
Neither two or value are known to C and that's OK"""
def __init__(self, one=undefined, **kwds):
locals_ = locals()
locals_ = {k:v for k,v in locals().items() if k not in ("self","kwds") and v is not undefined}
locals_.update(**kwds)
self.A = A(**locals_)
self.B = B(**locals_)
#can still use it locally
self.one = one if one is not undefined else 11
c4= C4(value="somevalue")
print("c4.A.one:", c4.A.one)
print("c4.A.two:", c4.A.two)
print("c4.one:", c4.one)
print("c4.B.value:", c4.B.value)
c4.A.one: 1
c4.A.two: 2
c4.one: 11
c4.B.value: somevalue
Upvotes: 0
Reputation: 1
Before the call class A and B, define init values to variables
Try add these before calls in class C init:
self.initA_one = A.one
self.initA_two = A.two
self.initB_value = B.value
And continue
self.A = A (.,.)
self.B = B (.)
EDIT:
this was what i meant.
class C():
def __init__(self, one=-1, two=-2, value="detest"):
self.initA_one = A().one
self.initA_two = A().two
self.initB = B().value
self.A = A(one, two)
self.B = B(value)
def do_smt(self):
print()
new_class = C()
print(f'default A.one is {new_class.initA_one}, new value A.one is {new_class.A.one}.')
print(f'default A.two is {new_class.initA_two}, new value A.two is {new_class.A.two}.')
print(f'default B.value is {new_class.initB}, new B.value is {new_class.B.value}')
gives
default A.one is 1, new value A.one is -1.
default A.two is 2, new value A.two is -2.
default B.value is test, new B.value is detest
Upvotes: 0
Reputation: 107095
You can use inspect.signature
to obtain the parameters of the __init__
method of each "base" class of class C
, and let C.__init__
accept variable keyword arguments, so that it can iterate through the "base" classes and pass to the __init__
method of each just what it needs and what the given keyword arguments have. Use itertools.islice
to ignore the first parameter, which is always self
:
import inspect
from itertools import islice
class C:
bases = A, B
params = {}
for cls in bases:
params[cls] = inspect.signature(cls.__init__).parameters
def __init__(self, **kwargs):
for cls in self.bases:
setattr(self, cls.__name__, cls(**{key: kwargs[key] for key in
islice(self.params[cls], 1, None) if key in kwargs}))
so that:
c = C(one=3,value='hi')
print(c.A.one)
print(c.A.two)
print(c.B.value)
outputs:
3
2
hi
Upvotes: 1
Reputation: 83577
One solution is to factor the default values to constants:
DEFAULT_ONE = 1
DEFAULT_TWO = 2
class A:
def __init__(self, one=DEFAULT_ONE, two=DEFAULT_TWO):
pass
Use the constants in class C
as well.
Upvotes: 1
Reputation: 73498
You could use some sentinel value (here None
) and pass parameters only if they are provided as something meaningful:
class C:
def __init__(self, one=None, two=None, value=None):
if one is two is None:
self.A = A()
else:
self.A = A(one, two)
if value is None:
self.B = B()
else:
self.B = B(value)
That way, A
and B
's defaults take care of themselves.
Upvotes: 0