Use an object method with the Initializer (Same line)
Asked Answered
Z

3

7

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?

Zwickau answered 7/4, 2016 at 18:8 Comment(2)
Are you sure you can't add *args and **kwargs to your __init__ function and do the work in the constructor?Jurisdiction
That's how I had it set up before, but my question uses an over-simplified example. To get the functionality I'm looking for, __init__ would require about 5+ default parameters.Zwickau
A
9

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()
Aretino answered 7/4, 2016 at 18:11 Comment(3)
Thanks I just tried your suggestion and it worked perfectly. The only thing I left out was "super(Storage, self).__init__()" What does this do?Zwickau
@PythonCheese It calls the __init__ method of the super class (which is now defined as object). The motivations for this are far beyond the scope of a comment.Jurisdiction
It's good practice in python to inherit from object so that your class is created as a new-style class (as opposed to an old-style class). The differences are subtle, but they could cause issues later on if you expand your class and intend to use it with other new-style classes (which make up the majority of classes these days). Calling the parent constructor is usually good practice as well, even though it really doesn't do anything here.Aretino
S
4

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')
...
Solange answered 7/4, 2016 at 18:28 Comment(1)
Thank you for answering. I chose Brenden's answer because it matched my needs. My object must be able to use multiple constructors and reuse them later. If I'm not mistaken, the methods above would create a completely new object each run. This will definitely come in handy, so thanks for the help.Zwickau
M
1

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()
Myrtismyrtle answered 5/7, 2023 at 1:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.