-1

If I need to have a function do some processing in order to initialize multiple of the object's variable (I'm having a hard time coming up with a simple example that doesn't seem weird).

Which of the following patterns is preferable?

class foo( object ):
    def __init__(self, bar, bar2):
        self.bar = bar
        self.baz, self.square_baz = self.func(bar2)

    def func(self, bar2):
        return self.bar + bar2, bar2 * ba2

Or

class foo( object ):
    def __init__(self, bar, bar2):
        self.bar = bar
        self.func(bar2)

    def func(self, bar2):
        self.baz = self.bar + bar2
        self.square_baz = bar2 * bar2

I feel like in the first pattern, the __init__ constructor and the processing func are nicely decoupled. And it's easy to tell what variables each instance of foo will contain. On the other hand, having to return multiple variables from a function to assign them to an object seems... ugly, yet this seems to be a consequence of the library I'm using.

  • 2
    I think some IDEs and linters look at __init__ to see what the attributes of the object are, so that could be a reason to set them in there. Not sure what they do with attributes set in functions called from __init__, but probably they don't track those. – RemcoGerlich Jan 17 '17 at 22:30
2

There is no official place to register all instance variables in Python, nor can there be, since one can add new instance variables dynamically, at any point in the object's lifetime.

Still, it's nice to have a single place to look (and for IDEs to look, as @RemcoGerlich says) to identify what instance variables are typically in play. My solution is to initialize all common instance variables in __init__, even if just to a dummy None value that will be quickly overwritten. This avoids the uglier multiple value assignment, yet gives humans and IDEs alike a single place to look for an overview of values.

class foo(object):
    def __init__(self, bar, bar2):
        self.bar = bar
        self.baz = None
        self.square_baz = None
        self.func(bar2)

    def func(self, bar2):
        self.baz = self.bar + bar2
        self.square_baz = bar2 * bar2

This example uses a standard, "public" method name func as the initializer. If func would normally be called from outside the class, this makes sense. Often, however, the function called from __init__ will be private (usually called only by the class, and either just once or infrequently). In that case, you can give further clarity by giving it a more indicative name, and by using an underscore prefix to the method name, which conventionally means "this is a private method." So I might call it _initialize or _finish_initialization for example. (Technically speaking, Python doesn't have "private methods," at least not in the enforced-scope way languages like Java do. Private methods are managed by convention/idiom, even if unenforced.)

  • I really like this approach for its clarity, but I wonder if there are performance concerns over assigning and reassigning, especially on the scale of millions of instances. – Michael Kolber Jul 25 at 21:44
  • 1
    @MichaelKolber No major performance concerns here. You're already working in Python, so have accepted some overhead such as a bytecode interpreter and instance attributes stored in dictionaries (unless you're using PyPy and/or __slots__). A few extra microseconds per instance creation to achieve clear initializations—it's not going to make or break any banks, including at high scale. – Jonathan Eunice Jul 25 at 21:55
1

Honestly, I wouldn't do either of those. func doesn't seem like it should be an instance method at all - consider, if someone had a foo object f, why would they ever want to call f.func()?

If bar and bar2 are likely to change during an object's lifetime (ie after __init__), then the best thing is probably just to:

class foo( object ):
    def __init__(self, bar, bar2):
        self.bar = bar
        self.bar2 = bar2

    @property
    def baz(self):
        return self.bar + self.bar2

    @property
    def square_baz(self):
        return self.bar2 * self.bar2

Then you could freely change f.bar and f.bar2 and you'd always be assured that f.baz and f.square_baz would remain in sync with them.

But, to be honest, I probably wouldn't do even that. I would probably just do the obvious thing (compute and assign everything in __init__).

class foo( object ):
    def __init__(self, bar, bar2):
        self.bar = bar
        self.baz = bar + bar2
        self.square_baz = bar2 * bar2

Without knowing why you can't or don't want to do that, I'm not sure what to recommend.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.