129

Should I give my class members default values like this:

class Foo:
    num = 1

or like this?

class Foo:
    def __init__(self):
        self.num = 1

In this question I discovered that in both cases,

bar = Foo()
bar.num += 1

is a well-defined operation.

I understand that the first method will give me a class variable while the second one will not. However, if I do not require a class variable, but only need to set a default value for my instance variables, are both methods equally good? Or one of them more 'pythonic' than the other?

One thing I've noticed is that in the Django tutorial, they use the second method to declare Models. Personally I think the second method is more elegant, but I'd like to know what the 'standard' way is.

Community
  • 1
  • 1
int3
  • 12,861
  • 8
  • 51
  • 80

6 Answers6

172

Extending bp's answer, I wanted to show you what he meant by immutable types.

First, this is okay:

>>> class TestB():
...     def __init__(self, attr=1):
...         self.attr = attr
...     
>>> a = TestB()
>>> b = TestB()
>>> a.attr = 2
>>> a.attr
2
>>> b.attr
1

However, this only works for immutable (unchangable) types. If the default value was mutable (meaning it can be replaced), this would happen instead:

>>> class Test():
...     def __init__(self, attr=[]):
...         self.attr = attr
...     
>>> a = Test()
>>> b = Test()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[1]
>>> 

Note that both a and b have a shared attribute. This is often unwanted.

This is the Pythonic way of defining default values for instance variables, when the type is mutable:

>>> class TestC():
...     def __init__(self, attr=None):
...         if attr is None:
...             attr = []
...         self.attr = attr
...     
>>> a = TestC()
>>> b = TestC()
>>> a.attr.append(1)
>>> a.attr
[1]
>>> b.attr
[]

The reason my first snippet of code works is because, with immutable types, Python creates a new instance of it whenever you want one. If you needed to add 1 to 1, Python makes a new 2 for you, because the old 1 cannot be changed. The reason is mostly for hashing, I believe.

Alec
  • 8,529
  • 8
  • 37
  • 63
Xavier Ho
  • 17,011
  • 9
  • 48
  • 52
  • 3
    I recently came across this much simpler way of implementing default values for mutable attributes. Just write `self.attr = attr or []` within the `__init__` method. It achieves the same result (I think) and is still clear and readable. – Bill Sep 03 '17 at 18:04
  • 13
    Sorry folks. I did some more research on the suggestion in my above comment and apparently it's not quite the same and is [not recommended](https://www.safaribooksonline.com/library/view/learning-python/1565924649/ch04s06.html). – Bill Sep 03 '17 at 18:18
  • 1
    Why does the class TestC have the empty parentheses? Is that a Python 2 legacy? – ChrisG Apr 12 '20 at 13:44
  • And a nice one-liner `self.attr = [] if attr is None else attr` – elano7 Mar 29 '23 at 15:45
69

The two snippets do different things, so it's not a matter of taste but a matter of what's the right behaviour in your context. Python documentation explains the difference, but here are some examples:

Exhibit A

class Foo:
  def __init__(self):
    self.num = 1

This binds num to the Foo instances. Change to this field is not propagated to other instances.

Thus:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1.num = 2
>>> foo2.num
1

Exhibit B

class Bar:
  num = 1

This binds num to the Bar class. Changes are propagated!

>>> bar1 = Bar()
>>> bar2 = Bar()
>>> bar1.num = 2 #this creates an INSTANCE variable that HIDES the propagation
>>> bar2.num
1
>>> Bar.num = 3
>>> bar2.num
3
>>> bar1.num
2
>>> bar1.__class__.num
3

Actual answer

If I do not require a class variable, but only need to set a default value for my instance variables, are both methods equally good? Or one of them more 'pythonic' than the other?

The code in exhibit B is plain wrong for this: why would you want to bind a class attribute (default value on instance creation) to the single instance?

The code in exhibit A is okay.

If you want to give defaults for instance variables in your constructor I would however do this:

class Foo:
  def __init__(self, num = None):
    self.num = num if num is not None else 1

...or even:

class Foo:
  DEFAULT_NUM = 1
  def __init__(self, num = None):
    self.num = num if num is not None else DEFAULT_NUM

...or even: (preferrable, but if and only if you are dealing with immutable types!)

class Foo:
  def __init__(self, num = 1):
    self.num = num

This way you can do:

foo1 = Foo(4)
foo2 = Foo() #use default
talekeDskobeDa
  • 372
  • 2
  • 13
badp
  • 11,409
  • 3
  • 61
  • 89
7

Using class members to give default values works very well just so long as you are careful only to do it with immutable values. If you try to do it with a list or a dict that would be pretty deadly. It also works where the instance attribute is a reference to a class just so long as the default value is None.

I've seen this technique used very successfully in repoze which is a framework that runs on top of Zope. The advantage here is not just that when your class is persisted to the database only the non-default attributes need to be saved, but also when you need to add a new field into the schema all the existing objects see the new field with its default value without any need to actually change the stored data.

I find it also works well in more general coding, but it's a style thing. Use whatever you are happiest with.

Duncan
  • 92,073
  • 11
  • 122
  • 156
4

With dataclasses, a feature added in Python 3.7, there is now yet another (quite convenient) way to achieve setting default values on class instances. The decorator dataclass will automatically generate a few methods on your class, such as the constructor. As the documentation linked above notes, "[t]he member variables to use in these generated methods are defined using PEP 526 type annotations".

Considering OP's example, we could implement it like this:

from dataclasses import dataclass

@dataclass
class Foo:
    num: int = 0

When constructing an object of this class's type we could optionally overwrite the value.

print('Default val: {}'.format(Foo()))
# Default val: Foo(num=0)
print('Custom val: {}'.format(Foo(num=5)))
# Custom val: Foo(num=5)
Johannes Gehrs
  • 722
  • 6
  • 13
  • 1
    Should mark the `num` attrib as a `dataclasses.field`, otherwise the propagation behaviour described in @badp's (sorry, can't get a mention right!) Exhibit B still occurs. ```python from dataclasses import dataclass, field @dataclass class Foo: num: int = field(default=0) ``` – kva1966 Oct 10 '21 at 16:15
  • 1
    Hi @kva1966 docs say that "The dataclass() decorator examines the class to find fields. A field is defined as a class variable that has a type annotation". Therefore my understanding is that the `int` type annotation is sufficient. – Johannes Gehrs Jan 13 '22 at 13:08
  • 1
    My apologies, you are right. I may have missed something in my earlier test. The type annotation is what makes the difference. – kva1966 Jan 17 '22 at 23:12
3

Using class members for default values of instance variables is not a good idea, and it's the first time I've seen this idea mentioned at all. It works in your example, but it may fail in a lot of cases. E.g., if the value is mutable, mutating it on an unmodified instance will alter the default:

>>> class c:
...     l = []
... 
>>> x = c()
>>> y = c()
>>> x.l
[]
>>> y.l
[]
>>> x.l.append(10)
>>> y.l
[10]
>>> c.l
[10]
Max Shawabkeh
  • 37,799
  • 10
  • 82
  • 91
  • except only the very first `[]` got cloned around there; `x.l` and `y.l` are both evil names and different immediate values linking to the same referent. (Language lawyer terms made up) – Phlip Dec 30 '15 at 19:09
2

You can also declare class variables as None which will prevent propagation. This is useful when you need a well defined class and want to prevent AttributeErrors. For example:

>>> class TestClass(object):
...     t = None
... 
>>> test = TestClass()
>>> test.t
>>> test2 = TestClass()
>>> test.t = 'test'
>>> test.t
'test'
>>> test2.t
>>>

Also if you need defaults:

>>> class TestClassDefaults(object):
...    t = None
...    def __init__(self, t=None):
...       self.t = t
... 
>>> test = TestClassDefaults()
>>> test.t
>>> test2 = TestClassDefaults([])
>>> test2.t
[]
>>> test.t
>>>

Of course still follow the info in the other answers about using mutable vs immutable types as the default in __init__.

Realistic
  • 1,038
  • 1
  • 10
  • 20