8

I have a class with a number of attributes. Could someone please clarify the differences between setting attributes like a,b,c below and x,y,z. I understand that if you arguments to come in they obviously must be in the init, however, just for setting a number default variables, which is preferred and what are the pros and cons.

class Foo(object):
    a = 'Hello'
    b = 1
    c = False
    def __init__(self):
        self.x = 'World'
        self.y = 2
        self.z = True
jonathanbsyd
  • 8,031
  • 6
  • 24
  • 26
  • 1
    possible duplicate of [Difference between defining a member in __init__ to defining it in the class body in python?](http://stackoverflow.com/questions/1549722/difference-between-defining-a-member-in-init-to-defining-it-in-the-class-bod) – millimoose Sep 13 '12 at 00:49
  • Did you know the mentioned duplicate itself has another possible duplicate ... Someone should build a graph chaining duplicate questions on SO ... – Samy Vilar Sep 13 '12 at 04:31

4 Answers4

8

Variables a, b, and c are class variables. They're evaluated and set once, when the class is first created. Variables x, y, and z are instance variables, which are evaluated and set whenever an object of that class is instantiated.

In general, you use class variables if the same values are going to be used by every instance of the class, and only needs to be calculated once. You use instance variables for variables that are going to be different for each instance of that class.

You can access class variables through the instance variable syntax self.a, but the object in question is being shared between all instances of the class. This doesn't affect much when using immutable data types such as integers or strings, but when using mutable data types such as lists, appending to self.a would cause all instances to see the newly-appended value.

Some examples from IDLE are probably helpful in understanding this:

>>> class Foo(object):
    a = 'Hello'
    b = []
    def __init__(self):
        self.y = []

>>> instance_1 = Foo()
>>> instance_2 = Foo()
>>> instance_1.a
'Hello'
>>> instance_2.a
'Hello'
>>> instance_1.a = 'Goodbye'
>>> instance_1.a
'Goodbye'
>>> instance_2.a
'Hello'
>>> instance_1.b
[]
>>> instance_2.b.append('12345')
>>> instance_1.b
['12345']
>>> instance_2.y
[]
>>> instance_2.y.append('abcde')
>>> instance_2.y
['abcde']
>>> instance_1.y
[]
Joe C.
  • 1,538
  • 11
  • 14
  • -1: This is misleading and not how Python class attributes (which includes methods!) work at all. They're an acceptable way of providing defaults for instance attributes. – millimoose Sep 13 '12 at 00:19
  • @millimoose, true, but not quite the whole picture. What you're saying happens to work for immutable types (such as int/str like the OP used), because when you do say `self.a = 'Byebye'` it'll be a new string and not change `Foo.a`. However, if `Foo.a` was a list, and you appended to `self.a` the original `Foo.a` would change too (it's the same object). Same "issue" with [mutable default parameters](http://effbot.org/zone/default-values.htm). – Ben Hoyt Sep 13 '12 at 00:41
  • @benhoyt I guess that makes my downvote an overreaction so I'll nuke it. Still, this is a Python behaviour that probably warrants explanation, not dogma, especially since it ties into the more general question of inadvertently sharing mutable objects which is not always so easily avoided. – millimoose Sep 13 '12 at 00:46
6

The main con / gotcha with using class attributes to provide default values for what you intend to be instance-specific data is that the default values are going to be shared between all instances of the class until the value is changed. E.g.:

class Foo(object):
    a = []

foo1 = Foo()
foo2 = Foo()

foo1.a.append(123)

foo1.a # [123]
foo2.a # [123]

However, the following will work as one might expect:

class Bar(object):
    a = 123    

bar1 = Bar()
bar2 = Bar()

bar1.a = 456

bar2.a # 123

To avoid this gotcha while using this technique, you should only use it to set defaults that are immutable values. (E.g. numbers, strings, tuples…)

The reason why Python behaves this way is that when you access an attribute with:

foo.bar

then bar is first looked up in the object foo. If the name in not found in the object (i.e. in foo.__dict__), then the name is looked up in the type of that object. For example, this mechanism is part of how method lookups work. (If you look at the __dict__ of an object, you'll notice its methods aren't there.)

Other minor issues are that this exposes the defaults through the type object when they're intended to be instance-specific; and that it mixes the definitions of class-specific attributes (like constants) if you have any with the defaults. The corollary of the former is that this will let you redefine the value of the default later on for all objects that haven't changed the value yet by assigning to the class attribute. (This could be useful, or confusing; the same precaution against mutable "global" variables applies.)

millimoose
  • 39,073
  • 9
  • 82
  • 134
2

As you've declared them, a, b, and c will be available without creating an instance of Foo.
For example, you could say:

Foo.a = "Goodbye"

In contrast, x, y, and z will not be created until you create an instance of Foo, and they are specific to that instance:

bar = Foo()
bar.x = 42
dckrooney
  • 3,041
  • 3
  • 22
  • 28
1

This has being asked countless of times ...

a,b,c are class variables shared among all instances, a good use would be a counter to count all the occurrences of this class that have being created, x,y,z are instance variables belonging to that single instance hence using self to set them ...

Note you can actually shadow class variables like self.a = 3 this does not change a but rather shadows it ... and has gotten a few people by surprise.

demo:

>>> class Foo(object):
...     a = 3
...     def __init__(self):
...         self.x = 6
... 
>>> Foo.a
3
>>> b = Foo()
>>> b.a = 5
>>> b.a
5
>>> Foo.a
3
>>> b.a
5
>>> Foo.a = 9
>>> Foo.a
9
>>> b.a
5
>>> c = Foo()
>>> c.a
9
Samy Vilar
  • 10,800
  • 2
  • 39
  • 34