Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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 editor 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?

share|improve this question
18  
Never let your IDE dictate what code you write? –  Martijn Pieters Aug 27 at 11:24
2  
By the way: using a proper python IDE (e.g. PyCharm), setting the attributes in the __init__ already provides autocompletion etc. Also, using None prevents the IDE to infer a better type for the attribute, so it's better to use a sensible default instead (when possible). –  Bakuriu Aug 27 at 17:39

5 Answers 5

up vote 24 down vote accepted

I call the latter bad practice under the "this dosen't 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."

share|improve this answer
1  
A bit like docstrings then ;-) Of course, Python has a handy mode to strip those, -OO, for those few who need it. –  Steve Jessop Aug 27 at 15:24
3  
The 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. –  delnan Aug 27 at 16:20
4  
@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~LL –  StarWeaver Aug 27 at 18:46
    
Actually, 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 Aug 27 at 20:48

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.

share|improve this answer
10  
You 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 Aug 27 at 12:34
3  
@MasonWheeler Python doesn't have overloading, so no class can have more than one constructor. –  delnan Aug 27 at 13:54
4  
Default 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 Aug 27 at 14:17
6  
@Benedict Python has no access control. The leading underscore is the accepted convention for implementation details. See PEP 8. –  Doval Aug 27 at 16:07
2  
@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?) –  delnan Aug 27 at 16:18

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
    # ...
share|improve this answer
    
Yeah, 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 Aug 27 at 18:54
    
Well, 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 need self to be passed, while class-level methods are unbound and are plain functions as seen at def time, and accept an instance as first argument. So these are different things. –  9000 Aug 28 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 Aug 28 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 Aug 28 at 2:54

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'
share|improve this answer
    
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 Aug 27 at 18:51
    
I should have elaborated that it can cause an error if used incorrectly. –  Daenyth Aug 27 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 Aug 27 at 20:39

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.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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