Use an object method with the Initializer (Same line)

Question:

I’m cleaning up a python object class, focusing mainly on how the object is created. The __init__ method creates a an empty dictionary that needs to be filled almost instantly. But this should NOT happen within the __init__, as the method used will vary widely. Here’s an example:

class Storage:

    def __init__(self):
        self.data = {}

    def fill_1(self):
        self.data['solo'] = 'all alone'

    def fill_2(self, buddy, bff):
        self.data['buddy'] = buddy
        self.data['bff'] = bff

    def fill_3(self, that_guy, house):
        self.data[that_guy] = house

Normally, I can just call one after the other like so:

box = Storage.Storage()
box.fill_1()

However, this can be overwhelming when I create many of these objects sequentially. My goal is to use the __init__ method with one of the fill methods on the same line. I’ve tried using the call below:

box = Storage.Storage().fill_1()

But this does not create the object and instead returns None. So I have two questions:

Is my code returning a None object because the line is calling an instance method?

And how can I create the Storage object and then call it’s fill method within the same line?

Asked By: Python Cheese

||

Answers:

This is not an idiom you tend to see that often in python (though it’s quite prevalent in many other languages, especially javascript), but you could do this by returning self from the mutator functions. (It looks like you were missing the self argument to the instance methods as well). This means you could also chain mutator calls — Storage().fill_1().fill_2()

class Storage(object):

    def __init__(self):
        super(Storage, self).__init__()
        data = {}

    def fill_1(self):
        data['solo'] = 'all alone'
        return self

    def fill_2(self, buddy, bff):
        data['buddy'] = buddy
        data['bff'] = bff
        return self

    def fill_3(self, that_guy, house):
        data[that_guy] = house
        return self

box = Storage().fill_1()
Answered By: Brendan Abel

Make alternate constructors:

class Storage(object):

    def __init__(self):
        self.data = {}

    @staticmethod
    def filled_1():
        obj = Storage()
        obj.data['solo'] = 'all alone'
        return obj

    @staticmethod
    def filled_2(self, buddy, bff):
        obj = Storage()
        obj.data['buddy'] = buddy
        obj.data['bff'] = bff
        return obj

    @staticmethod
    def filled_3(self, that_guy, house):
        obj = Storage()
        obj.data[that_guy] = house
        return obj

Then you don’t need to worry about separate creation and initialization calls, or muddle command-query separation with call chaining:

obj1 = Storage.filled_1()
obj2 = Storage.filled_2('Jenny', 'Joe')
...
Answered By: user2357112

Starting with Python 3.8, one can also use assignment expressions. Unlike the other two answers, which both require modifying the class itself, this can be used with any class:

(box := Storage()).fill_1()

This creates a new Storage instance, assigns it to box, and then calls its fill_1() method. It’s equivalent to:

box = Storage()
box.fill_1()
Answered By: Bob