In Python you may hear this phrase said a lot "everything is an object", which they are! Objects, that is. But what this means is that everything is really (if you look 'under the hood') either a class, or an instance of a class ("but what does that mean?!", don't worry, we'll get there). Once you have learnt how to make classes, and manipulate them, you gain the ability to create your own objects as well as edit existing ones as you may like. Now, this is a lot and naturally with great power comes great responsibility and it would be remiss for me to explain how you can break everything without explaining how to fix it as well.
However, ultimately, all this wild potential is well beyond even intermediate level and when you're just starting out you really only need the basics of classes and how they can be useful. Normally there's a lot of difficult syntax you need to learn, but in Python 3.7 they introduced dataclasses
which is a simple package that makes simple class creation and use easy.
Anatomy of a class in Python
Before I can explain how to use dataclasses
in meaningful detail, however, we need to go over what the various bits of a class are. Part 1 of this article is a quick primer of what a class looks like and what the various bits of it are called. I am using the built-in Python library dataclasses
to demonstrate them here which is actually a simplified form of regular classes, but that's the point. These are a lot easier than normal classes and I will get to what the syntax means and how to make your own in Part 2. For now, Part 1 is a quick guide to what we call all the bits and pieces that make up a typical class and its usage.
- Classes are defined like functions, except you use the keyword
class
instead ofdef
followed by the class name and then a colon.- Class names should always be in
UpperCamelCase
format, with the first letter of each word capitalised and with no spaces.
- Class names should always be in
from dataclasses import dataclass
@dataclass
class HatchBack:
make: str
model: str
weight: float
def get_summary(self):
return f'This Hatch back is a {self.make} {self.model} and it weighs {self.weight} kg'
car = HatchBack('Skoda', 'Fabia', 1200.)
summary = car.get_summary()
print(summary)
print(car.make)
- The code following the
class
name that is indented explains what the class is and how it behaves, like a function- the whole thing is known as the definition
- Any objects created from the class are known as instances of that class.
- in the code snippet above the variable
car
contains an instance ofHatchBack
.
- in the code snippet above the variable
- If a class has any functions defined in the class definition, these are often referred to as being "on" the class.
- Functions on a class are known as methods.
-
get_summary
is a method onHatchBack
. - "methods" are called using Python's dot notation, for example
car.get_summary()
in the preceding example. - Class methods should always* include the parameter
self
which refers to the object itself, get it?
-
- You can access, and modify, the variables "on" a class. These are known as "properties".
- For example, in the preceding example, the class properties are
make
,model
, andweight
. - Properties are accessed using dot notation.
- For example, in the preceding example, the class properties are
- This isn't strictly true as there are a method types that require other parameters instead of
self
, likeclassmethod
s andstaticmethod
s, but those are for another time.
- More on
self
.- You do not need to provide an argument corresponding to
self
** when calling a method, as shown in the preceding example. - Calling
self
"self" is actually a convention thing and you could technically use any name you like (so long as it's consistent within your class). - However, everyone uses
self
so you may just introduce a lot of unnecessary confusion if you decided to.
- You do not need to provide an argument corresponding to
** If you've ever called a method on an object in the past with an argument when you shouldn't have you may have seen
TypeError: HatchBack.get_summary() takes 1 positional argument but 2 were given
, or a variant, before. This is why!self
counts as a parameter and the argument is automatically supplied by Python at runtime, so if you try to add something in it complains of getting two arguments because Python already gave the method one when we weren't looking.
Decorators and the @
symbol
At this point I suspect you, as the reader, have a simple question in your mind.
"Hang on, are you just going to skip over what that
@
means in the example?"
Aha! You got me. Well done for noticing; that actually is an advanced form of Python syntax indicating the application of what's known as a "decorator". Decorators in Python are special functions that can be applied directly to existing functions that change their behaviour at runtime without needing to edit the code. They are functions that accept functions as arguments and return functions as output. They are an example of meta-programming.
What that word salad means is that decorators modify function and class definitions but preserve their names (unless the decorator explicitly affects the name), so you can still call them the same way. For example, you could write a simple decorator called print_my_args
which prints the arguments supplied to any function that the decorator has been applied to. Its application would look like this.
@print_my_args
def func(a, b, c):
return a*b + c
I hope to cover decorators in depth in a future article, but for now you should know that you only need to be able to recognise them and how to apply them at this level. Creating your own is advanced Python that is well beyond the level in this article series.
Other Reading
This is the first in a ten-part series on classes in python. For a complete look at the articles in this series, have a look at this overview page: Getting started with classes in Python