Dead Simple Python: Classes
Tags: python, oop, coding, beginners
Published: 2019-01-19 18:59:10

---
title: Dead Simple Python: Classes
published: true
description: Sure, Python can do object-oriented programming...but it's nothing like you'd expect.
tags: python, oop, coding, beginners
cover_image: https://thepracticaldev.s3.amazonaws.com/i/4j39ws0x86nyfgp378wx.jpg
series: Dead Simple Python
---

Like the articles? Buy the book! [***Dead Simple Python*** by Jason C. McDonald is available from No Starch Press.](https://nostarch.com/dead-simple-python)

---

Classes and objects: the bread-and-butter of many a developer. Object-oriented programming is one of the mainstays of modern programming, so it shouldn't come as a surprise that Python is capable of it.

But if you've done object-oriented programming in any other language before coming to Python, I can almost guarantee you're doing it wrong.

Hang on to your paradigms, folks, it's going to be a bumpy ride.

# Class Is In Session

Let's make a class, just to get our feet wet. Most of this won't come as a surprise to anyone.

```python
class Starship(object):

    sound = "Vrrrrrrrrrrrrrrrrrrrrr"

    def __init__(self):
        self.engines = False
        self.engine_speed = 0
        self.shields = True

    def engage(self):
        self.engines = True

    def warp(self, factor):
        self.engine_speed = 2
        self.engine_speed *= factor

    @classmethod
    def make_sound(cls):
        print(cls.sound)
```

Once that's declared, we can create a new **instance**, or **object**, from this class. All of the member functions and variables are accessible using **dot notation**.

```python
uss_enterprise = Starship()
uss_enterprise.warp(4)
uss_enterprise.engage()
uss_enterprise.engines
>>> True
uss_enterprise.engine_speed
>>> 8
```

> Wow, Jason, I knew this was supposed to be 'dead simple', but I think you just put me to sleep.

No surprises there, right? But look again, not at what *is* there, but what *isn't* there.

You can't see it? Okay, let's break this down. See if you can spot the surprises before I get to them.

# Declaration

We start with the definition of the class itself:

```python
class Starship(object):
```

Python might be considered one of the more truly object-oriented languages, on account of its design principle of "everything is an object." All other classes inherit from that `object` class.

Of course, most Pythonistas really hate boilerplate, so as of Python 3, we can also just say this and call it good:

```python
class Starship:
```

Personally, considering The Zen of Python's line about "Explicit is better than implicit," I like the first way. We could debate it until the cows come home, really, so let's just make it clear that both approaches do the same thing in Python 3, and move on.

**Legacy Note:** If you intend your code to work on Python 2, you *must* say `(object)`.

# Methods

I'm going to jump down to this line...

```python
def warp(self, factor):
```

Obviously, that's a **member function** or **method**. In Python, we pass `self` as the first parameters to every single method. After that, we can have as many parameters as we want, the same as with any other function.

We actually don't have to call that first argument `self`; it'll work the same regardless. But, we always use the name `self` there anyway, as a matter of style. There exists no valid reason to break that rule.

> "But, but...you literally *just* broke the rule yourself! See that next function?"

```python
@classmethod
def make_sound(cls):
```

You may remember that in object-oriented programming, a **class method** is one that is shared between all instances of the class (objects). A class method never touches member variables or regular methods.

If you haven't already noticed, we always access member variables in a class via the dot operator: `self.`. So, to make it extra-super-clear we can't do that in a class method, we call the first argument `cls`. In fact, when a class method is called, Python passes the *class* to that argument, instead of the *object*.

As before, we *can* call `cls` anything we want, but that doesn't mean we *should*.

For a class method, we also MUST put the **decorator** `@classmethod` on the line just above our function declaration. This tells the Python language that you're making a class method, and that you *didn't* just get creative with the name of the `self` argument.

Those methods above would get called something like this...

```python
uss_enterprise = Starship() # Create our object from the starship class

# Note, we aren't passing anything to 'self'. Python does that implicitly.
uss_enterprise.warp(4)

# We can call class functions on the object, or directly on the class.
uss_enterprise.make_sound()
Starship.make_sound()
```

Those last two lines will both print out "Vrrrrrrrrrrrrrrrrrrrrr" the exact same way. (Note that I referred to `cls.sound` in that function.)

...

What?

Come on, you know you made sound effects for your imaginary spaceships when you were a kid. Don't judge me.

# Class vs Static Methods

The old adage is true: you don't stop learning until you're dead. [Kinyanjui Wangonya](https://dev.to/wangonya) pointed out in the comments that one didn't need to pass `cls` to "static methods" - the phrase I was using in the first version of this article.

Turns out, he's right, and I was confused!

Unlike many other languages, **Python distinguishes between static and class methods.** Technically, they work the same way, in that they are both shared among all instances of the object. There's just one critical difference...

**A static method doesn't access any of the class members; it doesn't even care that it's part of the class!** Because it doesn't need to access any other part of the class, it doesn't need the `cls` argument.

Let's contrast a class method with a static method:

```python
@classmethod
def make_sound(cls):
    print(cls.sound)

@staticmethod
def beep():
    print("Beep boop beep")
```

Because `beep()` needs no access to the class, we can make it a static method by using the `@staticmethod` decorator. Python won't implicitly pass the class to the first argument, unlike what it does on a class method (`make_sound()`)

Despite this difference, you call both the same way.

```python
uss_enterprise = Starship()

uss_enterprise.make_sound()
>>> Vrrrrrrrrrrrrrrrrrrrrr
Starship.make_sound()
>>> Vrrrrrrrrrrrrrrrrrrrrr

uss_enterprise.beep()
>>> Beep boop beep
Starship.beep()
>>> Beep boop beep
```

# Initializers and Constructors

Every Python class needs to have one, and *only* one, `__init__(self)` function. This is called the **initializer**.

```python
def __init__(self):
    self.engine_speed = 1
    self.shields = True
    self.engines = False
```

If you really don't need an initializer, it is technically valid to skip defining it, but that's pretty universally considered bad form. In the very least, define an empty one...

```python
def __init__(self):
    pass
```

While we tend to use it the same way as we would a constructor in C++ and Java, `__init__(self)` is *not* a constructor! The initializer is responsible for initializing the **instance variables**, which we'll talk more about in a moment.

We rarely need to actually to define our own **constructor**. If you really know what you're doing, you can redefine the `__new__(cls)` function...

```python
def __new__(cls):
    return object.__new__(cls)
```

By the way, if you're looking for the **destructor**, that's the `__del__(self)` function.

# Variables

In Python, our classes can have **instance variables**, which are unique to our object (instance), and **class variables** (a.k.a. **static variables**), which belong to the class, and are shared between all instances.

I have a confession to make: **I spent the first few years of Python development doing this *absolutely and completely* wrong!** Coming from other object-oriented languages, I actually thought I was supposed to do this:

```python
class Starship(object):

    engines = False
    engine_speed = 0
    shields = True

    def __init__(self):
        self.engines = False
        self.engine_speed = 0
        self.shields = True

    def engage(self):
        self.engines = True

    def warp(self, factor):
        self.engine_speed = 2
        self.engine_speed *= factor
```

The code *works*, so what's wrong with this picture? Read it again, and see if you can figure out what's happening.

*Final Jeopardy music plays*

Maybe this will make it obvious.

```python
uss_enterprise = Starship()
uss_enterprise.warp(4)

print(uss_enterprise.engine_speed)
>>> 8
print(Starship.engine_speed)
>>> 0
```

Did you spot it?

Class variables are declared outside of all functions, usually at the top. Instance variables, on the other hand, are declared in the `__init__(self)` function: for example, `self.engine_speed = 0`.

So, in our little example, we've declared a set of class variables, and a set of instance variables, with the same names. When accessing a variable on the object, the instance variables **shadow** (hide) the class variables, making it behave as we might expect. However, we can see by printing `Starship.engine_speed` that we have a separate class variable sitting in the class, just taking up space. Talk about redundant.

Anyone get that right? Sloan did, and wagered...ten thousand cecropia leaves. Looks like the sloth is in the lead. Amazingly enough.

By the way, you *can* declare instance variables for the first time from within any instance method, instead of the initializer. However...you guessed it: *don't*. The convention is to ALWAYS declare all your instance variables in the initializer, just to prevent something weird from happening, like a function attempting to access a variable that doesn't yet exist.

# Scope: Private and Public

If you come from another object-oriented language, such as Java and C++, you're also probably in the habit of thinking about **scope** (private, protected, public) and its traditional assumptions: variables should be private, and functions should (usually) be public. Getters and setters rule the day!

I'm also an expert in C++ object-oriented programming, and I have to say that I consider Python's approach to the issue of scope to be vastly superior to the typical object-oriented scope rules. Once you grasp how to design classes in Python, the principles will probably leak into your standard practice in other languages...and I firmly believe that's a good thing.

Ready for this? **Your variables don't actually need to be private.**

Yes, I just heard the gurgling scream of the Java nerd in the back. "But...but...how will I keep developers from just tampering with any of the object's instance variables?"

Often, that concern is built on three flawed assumptions. Let's set those right first:

+ The developer using your class almost certainly isn't in the habit of modifying member variables directly, any more than they're in the habit of sticking a fork in a toaster.

+ If they *do* stick a fork in the toaster, proverbially speaking, the consequences are on them for being idiots, not on you.

+ As my Freenode #python friend `grym` once said, "if you know *why* you aren't supposed to remove stuck toast from the toaster with a metal object, you're allowed to do so."

In other words, the developer who is using your class probably knows better than you do about whether they should twiddle the instance variables or not.

Now, with that out of the way, we approach an important premise in Python: **there is no actual 'private' scope**. We can't just stick a fancy little keyword in front of a variable to make it private.

What we *can* do is stick an underscore at the front of the name, like this: `self._engine`.

That underscore isn't magical. It's just a warning label to anyone using your class: "I recommend you don't mess with this. I'm doing something special with it."

Now, before you go sticking `_` at the start of all your instance variable names, think about what the variable actually *is*, and how you use it. Will directly tweaking it really cause problems? In the case of our example class, as it's written right now, no. This actually would be perfectly acceptable:

```python
uss_enterprise.engine_speed = 6
uss_enterprise.engage()
```

Also, notice something beautiful about that? **We didn't write a single getter or setter!** In any language, if a getter or setter are functionally identical to modifying the variable directly, they're an absolute waste. That philosophy is one of the reasons Python is such a *clean* language.

You can also use this naming convention with methods you don't intend to be used outside of the class.

**Side Note:** Before you run off and go eschew `private` and `protected` from your Java and C++ code, please understand that there's a time and a place for scope. The underscore convention is a *social contract* among Python developers, and most languages don't have anything like that. So, if you're in a language with scope, use `private` or `protected` on any variable you would have put an underscore in front of in Python.

## Private...Sort Of

Now, on a very rare occasion, you may have an instance variable which *absolutely, positively, never, ever* should be directly modified outside of the class. In that case, you may precede the name of the variable with *two* underscores (`__`), instead of one.

This doesn't actually make it private; rather, it performs something called **name mangling**: it changes the name of the variable, adding a single underscore and the name of the class on the front.

In the case of `class Starship`, if we were to change `self.shields` to `self.__shields`, it would be name mangled to `self._Starship__shields`.

So, if you know how that name mangling works, you can still access it...

```python
uss_enterprise = Starship()
uss_enterprise._Starship__shields
>>> True
```

It's important to note, you also cannot have more than one trailing underscore if this is to work. (`__foo` and `__foo_` will be mangled, but `__foo__` will not). But then, [PEP 8 generally discourages trailing underscores](https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles), so it's kinda a moot point.

By the way, the purpose of the double underscore (`__`) name mangling actually has **nothing to do** with private scope; it's all about preventing name conflicts with some technical scenarios. In fact, you'll probably get a few serious frowns from Python ninjas for employing `__` at all, so use it sparingly.

# Properties

As I said earlier, getters and setters are usually pointless. On occasion, however, they have a purpose. In Python, we can use **properties** in this manner, as well as to pull off some pretty nifty tricks!

Properties are defined simply by preceding a method with `@property`.

My favorite trick with properties is to make a method look like an instance variable...

```python
class Starship(object):

    def __init__(self):
        self.engines = True
        self.engine_speed = 0
        self.shields = True
    
    @property
    def engine_strain(self):
        if not self.engines:
            return 0
        elif self.shields:
            # Imagine shields double the engine strain
            return self.engine_speed * 2
        # Otherwise, the engine strain is the same as the speed
        return self.engine_speed
```

When we're using this class, we can treat `engine_strain` as an instance variable of the object.

```python
uss_enterprise = Starship()
uss_enterprise.engine_strain
>>> 0
```

Beautiful, isn't it?

(Un)fortunately, we cannot *modify* `engine_strain` in the same manner.

```python
uss_enterprise.engine_strain = 10
>>> Traceback (most recent call last):
>>>   File "<stdin>", line 1, in <module>
>>> AttributeError: can't set attribute
```

In this case, that actually does make sense, but it might not be what you're wanting other times. Just for fun, let's define a setter for our property too; at least one with nicer output than that scary error.

```python
@engine_strain.setter
def engine_strain(self, value):
    print("I'm giving her all she's got, Captain!")
```

We precede our method with the decorator `@NAME_OF_PROPERTY.setter`. We also have to accept a single `value` argument (after `self`, of course), and positively nothing beyond that. You'll notice we're not actually doing anything with the `value` argument in this case, and that's fine for our example.

```python
uss_enterprise.engine_strain = 10
>>> I'm giving her all she's got, Captain!
```

That's much better.

As I mentioned earlier, we can use these as getters and setters for our instance variables. Here's a quick example of how:

```python
class Starship:
    def __init__(self):
        # snip
        self._captain = "Jean-Luc Picard"

    @property
    def captain(self):
        return self._captain

    @captain.setter
    def captain(self, value):
        print("What do you think this is, " + value + ", the USS Pegasus? Back to work!")
```

We simply preceded the variable these functions concern with an underscore, to indicate to others that we intend to manage the variable ourselves. The getter is pretty dull and obvious, and is only needed to provide expected behavior. The setter is where things are interesting: we knock down any attempted mutinies. There will be no changing this captain!

```python
uss_enterprise = Starship()
uss_enterprise.captain
>>> 'Jean-Luc Picard'
uss_enterprise.captain = "Wesley"
>>> What do you think this is, Wesley, the USS Pegasus? Back to work!
```

**Technical rabbit trail:** if you want to create *class* properties, that requires some hacking on your part. There are several solutions floating around the net, so if you need this, go research it!

A few of the Python nerds will be on me if I didn't point out, there is another way to create a property without the use of decorators. So, just for the record, this works too...

```python
class Starship:
    def __init__(self):
        # snip
        self._captain = "Jean-Luc Picard"

    def get_captain(self):
        return self._captain

    def set_captain(self, value):
        print("What do you think this is, " + value + ", the USS Pegasus? Back to work!")

    captain = property(get_captain, set_captain)
```

(Yes, that last line exists outside of any function.)

As usual, the [documentation on properties](https://docs.python.org/3/library/functions.html?highlight=property#property) has additional information, and some more nifty tricks with properties.

# Inheritance

Finally, we come back to that first line for another look.

```python

class Starship(object):
```

Remember why that `(object)` is there? We're inheriting from Python's `object` class. Ahh, inheritance! That's where it belongs.

```python
class USSDiscovery(Starship):
    
    def __init__(self):
        super().__init__()
        self.spore_drive = True
        self._captain = "Gabriel Lorca"
```

The only real mystery here is that `super().__init__()` line. In short, `super()` refers to the class we inherited from (in this case, `Starship`), and calls its initializer. We need to call this, so `USSDiscovery` has all the same instance variables as `Starship`.

Of course, we can define new instance variables (`self.spore_drive`), and redefine inherited ones (`self._captain`).

We could have actually just called that initializer with `Starship.__init__()`, but then if we wanted to change what we inherit from, we'd have to change that line too. The `super().__init__()` approach is ultimately just cleaner and more maintainable.

**Legacy Note:** By the way, if you're using Python 2, that line is a little uglier: `super(USSDiscovery, self).__init__()`.

Before you ask: YES, you can do multiple inheritance with `class C(A, B):`. It actually works better than in most languages! Regardless, but you can count on a [side order of headaches](https://stackoverflow.com/q/3277367/472647), especially when using `super()`.

# Hold the Classes!

As you can see, Python classes are a little different from other languages, but once you're used to them, they're actually a bit easier to work with.

But if you've coded in class-heavy languages like C++ or Java, and are working on the assumption that you need classes in Python, I have a surprise for you. **You really aren't required to use classes at all!**

Classes and objects have exactly one purpose in Python: **data encapsulation**. If you need to keep data and the functions for manipulating it together in a handy unit, classes are the way to go. Otherwise, don't bother! There's absolutely nothing wrong with a module composed entirely of functions.

# Review

Whew! You still with me? How many of those surprises about classes in Python did you guess?

Let's review...

+ The `__init__(self)` function is the **initializer**, and that's where we do all of our variable initialization.

+ **Methods** (member functions) must take `self` as their first argument.

+ **Class methods** must take `cls` as their first argument, and have the decorator `@classmethod` on the line just above the function definition. They can access class variables, but not instance variables.

+ **Static methods** are similar to class methods, except they *don't* take `cls` as their first argument, and are preceded by the decorator `@staticmethod`. They cannot access any class or instance variables or functions. They don't even know they're part of a class.

+ **Instance variables** (member variables) should be declared **inside** `__init__(self)` first. We don't declare them outside of the constructor, unlike most other object-oriented languages.

+ **Class variables** or **static variables** are declared outside of any function, and are shared between all instances of the class.

+ There are no private members in Python! Precede a member variable or a method name with an underscore (`_`) to tell developers they shouldn't mess with it.

+ If you precede a member variable or method name with two underscores (`__`), Python will change its name using *name mangling*. This is more for preventing name conflicts than hiding things.

+ You can make any method into a **property** (it looks like a member variable) by putting the decorator `@property` on the line above its declaration. This can also be used to create *getters*.

+ You can create a *setter* for a property (e.g. `foo`) by putting the decorator `@foo.setter` above a function `foo`.

+ A class (e.g. `Dog`) can inherit from another class (e.g. `Animal`) in this manner: `class Dog(Animal):`. When you do this, you should also start your initializer with the line `super().__init__()` to call the initializer of the base class.

+ Multiple inheritance is possible, but it might give you nightmares. Handle with tongs.

As usual, I recommend you read the docs for more:

+ [Python Tutorials: Classes](https://docs.python.org/3/tutorial/classes.html)
+ [Python Reference: Built-In Functions - @classmethod](https://docs.python.org/3/library/functions.html?highlight=classmethod#classmethod)
+ [Python Reference: Built-In Functions - @staticmethod](https://docs.python.org/3/library/functions.html?highlight=staticmethod#staticmethod)
+ [Python Reference: Built-In Functions - @property](https://docs.python.org/3/library/functions.html?highlight=property#property)

Ready to go write some Python classes? Make it so!

---

*Thank you to `deniska`, `grym` (Freenode IRC `#python`), and @wangonya (Dev) for suggested revisions.*