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 Answers 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– Michael Kolber2019年07月25日 21:44:25 +00:00Commented Jul 25, 2019 at 21:44
-
2@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– Jonathan Eunice2019年07月25日 21:55:45 +00:00Commented Jul 25, 2019 at 21:55
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.
__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.