user2874583
user2874583

Reputation: 535

Use default init values of other classes

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

Answers (6)

kadee
kadee

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

JL Peyret
JL Peyret

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)

output:

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)

output:

c4.A.one: 1
c4.A.two: 2
c4.one: 11
c4.B.value: somevalue

Upvotes: 0

leolynx
leolynx

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

blhsing
blhsing

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

Code-Apprentice
Code-Apprentice

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

user2390182
user2390182

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

Related Questions