Besides making a distinction between class methods and instance methods, Python’s object model also distinguishes between class and instancesvariables.
It’s an important distinction, but also one that caused me trouble as a new Python developer. For a long time I didn’t invest the time needed to understand these concepts from the ground up. And so my early OOP experiments were riddled with surprising behaviors and odd bugs. In this chapter we’ll clear up any lingering confusion about this topic with some hands-on examples.
Like I said, there are two kinds of data attributes on Python objects:
class variablesandinstance variables.
Class variablesare declared inside the class definition (but outside of any instance methods). They’re not tied to any particular instance of a class. Instead, class variables store their contents on the class itself, and all objects created from a particular class share access to the same set of class variables. This means, for example, that modifying a class variable affects all object instances at the same time.
Instance variables are always tied to a particular object instance.
Their contents are not stored on the class, but on each individual ob-ject created from the class. Therefore, the contents of an instance vari-able are completely independent from one object instance to the next.
And so, modifying an instance variable only affects one object instance at a time.
Okay, this was fairly abstract—time to look at some code! Let’s bust out the old “dog example”… For some reason, OOP-tutorials always use cars or pets to illustrate their point, and it’s hard to break with that tradition.
What does a happy dog need? Four legs and a name:
class Dog:
num_legs = 4 # <- Class variable
def __init__(self, name):
self.name = name # <- Instance variable
Alright, that’s a neat object-oriented representation of the dog situa-tion I just described. Creating newDoginstances works as expected, and they each get an instance variable calledname:
>>> jack = Dog('Jack')
>>> jill = Dog('Jill')
>>> jack.name, jill.name
('Jack', 'Jill')
There’s a little more flexibility when it comes to class variables. You can access thenum_legsclass variable either directly on eachDog in-stance oron the class itself:
>>> jack.num_legs, jill.num_legs
(4, 4)
>>> Dog.num_legs
4
However, if you try to access aninstancevariable through the class, it’ll fail with anAttributeError. Instance variables are specific to each object instance and are created when the__init__constructor runs—they don’t even exist on the class itself.
This is the central distinction between class and instance variables:
>>> Dog.name
AttributeError:
"type object 'Dog' has no attribute 'name'"
Alright, so far so good.
Let’s say that Jack the Dog gets a little too close to the microwave when he eats his dinner one day—and he sprouts an extra pair of legs. How’d you represent that in the little code sandbox we’ve got so far?
The first idea for a solution might be to simply modify the num_legs variable on theDogclass:
>>> Dog.num_legs = 6
But remember, we don’t want alldogs to start scurrying around on six legs. So now we’ve just turned every dog instance in our little uni-verse into Super Dog because we’ve modified aclass variable. And this affects all dogs, even those created previously:
>>> jack.num_legs, jill.num_legs
(6, 6)
So that didn’t work. The reason it didn’t work is that modifying a class variableon the class namespaceaffects all instances of the class. Let’s roll back the change to the class variable and instead try to give an extra pair o’ legs specifically to Jack only:
>>> Dog.num_legs = 4
>>> jack.num_legs = 6
Now, what monstrosities did this create? Let’s find out:
>>> jack.num_legs, jill.num_legs, Dog.num_legs
(6, 4, 4)
Okay, this looks “pretty good” (aside from the fact that we just gave poor Jack some extra legs). But how did this change actually affect ourDogobjects?
You see, the trouble here is that while we got the result we wanted (ex-tra legs for Jack), we introduced anum_legsinstance variable to the Jack instance. And now the newnum_legsinstance variable “shad-ows” the class variable of the same name, overriding and hiding it when we access the object instance scope:
>>> jack.num_legs, jack.__class__.num_legs
(6, 4)
As you can see, the class variables seemingly gotout of sync. This hap-pened because writing tojack.num_legscreated aninstance variable with the same name as the class variable.
This isn’t necessarily bad, but it’s important to be aware of what hap-pened here, behind the scenes. Before I finally understood class-level and instance-level scope in Python, this was a great avenue for bugs to slip into my programs.
To tell you the truth, trying to modify a class variable through an object instance—which then accidentally creates an instance variable of the same name, shadowing the original class variable—is a bit of an OOP pitfall in Python.
A Dog-free Example
While no dogs were harmed in the making of this chapter (it’s all fun and games until someone sprouts and extra pair of legs), I wanted to give you one more practical example of the useful things you can do with class variables. Something that’s a little closer to the real-world applications for class variables.
So here it is. The followingCountedObjectclass keeps track of how many times it was instantiated over the lifetime of a program (which might actually be an interesting performance metric to know):
class CountedObject:
num_instances = 0
def __init__(self):
self.__class__.num_instances += 1
CountedObjectkeeps anum_instancesclass variable that serves as a shared counter. When the class is declared, it initializes the counter to zero and then leaves it alone.
Every time you create a new instance of this class, it increments the shared counter by one when the__init__constructor runs:
>>> CountedObject.num_instances
0
>>> CountedObject().num_instances
1
>>> CountedObject().num_instances
2
>>> CountedObject().num_instances
3
>>> CountedObject.num_instances
3
Notice how this code needs to jump through a little hoop to make sure it increments the counter variableon the class. It would’ve been an easy mistake to make if I had written the constructor as follows:
# WARNING: This implementation contains a bug
class BuggyCountedObject:
num_instances = 0 def __init__(self):
self.num_instances += 1 # !!!
As you’ll see, this (bad) implementation never increments the shared
I’m sure you can see where I went wrong now. This (buggy) im-plementation never increments the shared counter because I made the mistake I explained in the “Jack the Dog” example earlier. This implementation won’t work because I accidentally “shadowed” the num_instanceclass variable by creating an instance variable of the same name in the constructor.
It correctly calculates the new value for the counter (going from 0 to 1), but then stores the result in an instance variable—which means other instances of the class never even see the updated counter value.
As you can see, that’s quite an easy mistake to make. It’s a good idea to be careful and double-check your scoping when dealing with shared state on a class. Automated tests and peer reviews help greatly with that.
Nevertheless, I hope you can see why and how class variables—despite their pitfalls—can be useful tools in practice. Good luck!
Key Takeaways
• Class variables are for data shared by all instances of a class.
They belong to a class, not a specific instance and are shared
among all instances of a class.
• Instance variables are for data that is unique to each instance.
They belong to individual object instances and are not shared among the other instances of a class. Each instance variable gets a unique backing store specific to the instance.
• Because class variables can be “shadowed” by instance vari-ables of the same name, it’s easy to (accidentally) override class variables in a way that introduces bugs and odd behavior.