Consider the following class:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
My coworkers tend to define it like this:
class Person:
name = None
age = None
def __init__(self, name, age):
self.name = name
self.age = age
The main reason for this is that their IDE of choice shows the properties for autocompletion.
Personally, I dislike the latter one, because it makes no sense that a class has those properties set to None
.
Which one would be better practice and for what reasons?
5 Answers 5
I call the latter bad practice under the "this does not do what you think it does" rule.
Your coworker's position can be rewritten as: "I am going to create a bunch of class-static quasi-global variables which are never accessed, but which do take up space in the various class's namespace tables (__dict__
), just to make my IDE do something."
-
6A bit like docstrings then ;-) Of course, Python has a handy mode to strip those,
-OO
, for those few who need it.Steve Jessop– Steve Jessop2014年08月27日 15:24:52 +00:00Commented Aug 27, 2014 at 15:24 -
18The space taken is by far the least important reason this convention stinks. It's a dozen bytes per name (per process). I feel like having wasted my time just reading the part of your answer pertaining to it, that's how unimportant it the space cost.user7043– user70432014年08月27日 16:20:03 +00:00Commented Aug 27, 2014 at 16:20
-
9@delnan I agree about the memory size of the entries being meaningless, i was more thinkin gof the logical/mental space taken up, making introspective debugging and such require more reading and sorting, i would guess. I~LLWeaver– Weaver2014年08月27日 18:46:46 +00:00Commented Aug 27, 2014 at 18:46
-
3Actually, if you were this paranoid about space, you would notice that this saves space, and wastes time. The uninitialized members fall through to use the class value, and therefore there aren't a bunch of copies of the variable name mapped to None (one for each instance). So the cost is a few bytes at the class and the savings is a few bytes per instance. But each failing variable name search costs a tiny bit of time.Jon Jay Obermark– Jon Jay Obermark2014年08月27日 20:48:49 +00:00Commented Aug 27, 2014 at 20:48
-
3If you were worried about 8 bytes you wouldn't be using Python.c z– c z2018年04月30日 13:58:26 +00:00Commented Apr 30, 2018 at 13:58
1. Make your code easy to understand
Code is read much more often than written. Make your code maintainer's task easier (it as well may be yourself next year).
I don't know about any hard rules, but I prefer to have any future instance state clearly declared outright. Crashing with an AttributeError
is bad enough. Not seeing clearly the lifecycle of an instance attribute is worse. The amount of mental gymnastic required to restore possible call sequences that lead to the attribute being assigned can easily become non-trivial, leading to errors.
So I usually not only define everything in constructor, but also strive to keep the number of mutable attributes to a minimum.
2. Don't mix class-level and instance-level members
Anything you define right inside the class
declaration belongs to the class and is shared by all instances of the class. E.g. when you define a function inside a class, it becomes a method which is the same for all instances. Same applies to data members. This is totally unlike instance attributes you usually define in __init__
.
Class-level data members are most useful as constants:
class Missile(object):
MAX_SPEED = 100 # all missiles accelerate up to this speed
ACCELERATION = 5 # rate of acceleration per game frame
def move(self):
self.speed += self.ACCELERATION
if self.speed > self.MAX_SPEED:
self.speed = self.MAX_SPEED
# ...
-
2Yeah, but mixing class-level and instance-level members is pretty much what def does. It creates functions that we think of as attributes of objects, but are really members of the class. The same thing goes for property and its ilk. Giving the illusion that work belongs to objects when it is really mediated by the class is a time-honored way of not going nuts. How can it be all that bad, if it is OK for Python itself.Jon Jay Obermark– Jon Jay Obermark2014年08月27日 18:54:38 +00:00Commented Aug 27, 2014 at 18:54
-
1Well, method resolution order in Python (also applicable to data members) is not very simple. What is not found on instance level, will be searched for on class level, then among base classes, etc. You can indeed shadow a class-level member (data or method) by assigning a same-named instance-level member. But instance-level methods are bound to instance's
self
and don't needself
to be passed, while class-level methods are unbound and are plain functions as seen atdef
time, and accept an instance as first argument. So these are different things.9000– 90002014年08月28日 00:05:09 +00:00Commented Aug 28, 2014 at 0:05 -
I think an
AttributeError
is a a good signal than there is a bug. Otherwise, you would swallow the None and get meaningless results. Specially important in the case at hand, where the attributes are defined in the__init__
, so a missing attribute (but existing at a class level) could only be caused by buggy inheritance.Davidmh– Davidmh2014年08月28日 00:11:10 +00:00Commented Aug 28, 2014 at 0:11 -
@Davidmh: A detected error is always better than an undetected error, indeed! I'd say that if you have to make an attribute
None
and this value does not make sense at the moment of the instance construction, you have a problem in your architecture and have to rethink the life cycle of the attribute's value or its initial value. Note that by defining your attributes early you can detect such problem even before you wrote the rest of the class, let alone running the code.9000– 90002014年08月28日 02:54:36 +00:00Commented Aug 28, 2014 at 2:54 -
Fun! missiles! anyway, i'm pretty sure it's OK to make class level vars and mix them... as long as the class level contains defaults, etc.Erik Aronesty– Erik Aronesty2019年09月03日 18:31:47 +00:00Commented Sep 3, 2019 at 18:31
Personally I define the members in the __ init__() method. I never thought about defining them in the class part. But what I always do: I init all of the members in the __ init__ method, even those not needed in the __ init__ method.
Example:
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
self._selected = None
def setSelected(self, value):
self._selected = value
I think it is important to define all members in one place. It makes the code more readable. Whether it is inside __ init__() or outside, is not that important. But it is important for a team to commit to more or less the same coding style.
Oh, and you may notice I ever add the prefix "_" to member variables.
-
14You should set all values in the constructor. If the value was set in the class itself, it would be shared between instances, which is fine for None, but not for most values. So don't change anything about it. ;)Remco Haszing– Remco Haszing2014年08月27日 12:34:15 +00:00Commented Aug 27, 2014 at 12:34
-
4Default values for parameters, and the ability to take arbitrary positional and keyword arguments makes it much less painful than one would expect. (And it is actually possible to delegate construction to other methods or even stand-alone functions, so if you really need multiple dispatch you can do that too).Sean Vieira– Sean Vieira2014年08月27日 14:17:14 +00:00Commented Aug 27, 2014 at 14:17
-
6
-
3@Doval There is an extraordinarily good reason to prefix attribute names with
_
: To indicate that it is private! (Why are so many people in this thread confusing or half-confusing Python with other languages?)user7043– user70432014年08月27日 16:18:29 +00:00Commented Aug 27, 2014 at 16:18 -
1Right, so if 'ever' means 'always' in the answer, then stop! Do it only when you wish to emphasize limited access.Jon Jay Obermark– Jon Jay Obermark2014年08月27日 20:45:32 +00:00Commented Aug 27, 2014 at 20:45
This is a bad practice. You don't need those values, they clutter up the code, and they can cause errors.
Consider:
>>> class WithNone:
... x = None
... y = None
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
>>> class InitOnly:
... def __init__(self, x, y):
... self.x = x
... self.y = y
...
>>> wn = WithNone(1,2)
>>> wn.x
1
>>> WithNone.x #Note that it returns none, no error
>>> io = InitOnly(1,2)
>>> InitOnly.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class InitOnly has no attribute 'x'
-
I would be hard-pressed to call that 'causing an error'. It is ambiguous what you mean by requesting 'x' here, but you might very well want the most likely initial value.Jon Jay Obermark– Jon Jay Obermark2014年08月27日 18:51:11 +00:00Commented Aug 27, 2014 at 18:51
-
I should have elaborated that it can cause an error if used incorrectly.Daenyth– Daenyth2014年08月27日 19:41:21 +00:00Commented Aug 27, 2014 at 19:41
-
The higher risk is still initializing to an object. None really is pretty safe. The only error it is going to cause is to swallow really lame-brained exceptions.Jon Jay Obermark– Jon Jay Obermark2014年08月27日 20:39:20 +00:00Commented Aug 27, 2014 at 20:39
-
2Failing to cause errors is a problem if the errors would have prevented more serious issues.Erik Aronesty– Erik Aronesty2019年09月03日 18:35:07 +00:00Commented Sep 3, 2019 at 18:35
I will go with "a bit like docstrings, then" and declare this harmless, as long as it is always None
, or a narrow range of other values, all immutable.
It reeks of atavism, and excessive attachment to statically typed languages. And it does no good as code. But it has a minor purpose that remains, in documentation.
It documents what the expected names are, so if I combine code with someone and one of us has 'username' and the other user_name, there is a clue to the humans that we have parted ways and are not using the same variables.
Forcing full initialization as a policy achieves the same thing in a more Pythonic way, but if there is actual code in the __init__
, this provides a clearer place to document the variables in use.
Obviously the BIG problem here is that it tempts folks to initialize with values other than None
, which can be bad:
class X:
v = {}
x = X()
x.v[1] = 2
leaves a global trace and does not create an instance for x.
But that is more of a quirk in Python as a whole than in this practice, and we should already be paranoid about it.
__init__
already provides autocompletion etc. Also, usingNone
prevents the IDE to infer a better type for the attribute, so it's better to use a sensible default instead (when possible).typing
module, which allow you to provide hints to the IDE and linter, if that sort of thing tickles your fancy...self
. Even ifself.name
orself.age
were not assigned in__init__
they would not show up in the instanceself
, they only show up in the classPerson
.