dekuShrub
dekuShrub

Reputation: 486

Calling constructor of class as default argument in other class constructor

problem

I tried to call the constructor of a class inside another class' constructor's parameter's default assignment, but I encountered this problem where the constructor isn't called correctly. What is happening here?

code explanation

The Bad class is the case that doesn't work and the Good class is an ugly work around to solve the problem. The value Base.i is incremented for every time the constructor Base.__init__ is called, and as can be seen, it isn't incremented correctly for the o2 object, but it seems to be incremented correctly for each of o1, o3 and o4.

code

class Base:
    i = 0
    def __init__(self):
        Base.i += 1
        self.i = Base.i

class Bad:
    def __init__(self, base = Base()):
        self.base = base

class Good:
    def __init__(self, base = None):
        if base is None:
            self.base = Base()
        else:
            self.base = base

if __name__ == "__main__":
    o1 = Bad()
    o2 = Bad()

    print("Bad:")
    print(f"o1.i: {o1.base.i}")
    print(f"o2.i: {o2.base.i}")

    o3 = Good()
    o4 = Good()

    print("Good:")
    print(f"o3.i: {o3.base.i}")
    print(f"o4.i: {o4.base.i}")

output

o1.i: 1
o2.i: 1
Good:
o3.i: 2
o4.i: 3

Upvotes: 0

Views: 782

Answers (1)

Jan Christoph Terasa
Jan Christoph Terasa

Reputation: 5935

Default arguments are evaluated at definition time, so here

class Bad:
    def __init__(self, base = Base()):
        self.base = base

the name base (and hence self.base) is assigned an instance of Base at the time of definition already.

One way is to use a default of None, as you already do in class Good

class Good:
    def __init__(self, base = None):
        if base is None:
            self.base = Base()
        else:
            self.base = base

You could also define the __init__ of Bad like this:

class Bad:
    def __init__(self, base = Base):
        self.base = base()

which will assign an instance of Base when Bad is initialized, not at definition time, so each instance of Bad will have its own Base.

If you want to able to pass arguments to base, you'd have to add options

class Bad:
    def __init__(self, base = Base, base_args=(,), base_kwargs={}):
        self.base = base(*base_args, **base_kwargs)

but in that case the None way is simpler to use, since you can just pass the constructed instance directly.

Upvotes: 5

Related Questions