Python Class Example
In this tutorial, we’ll talk about classes. By using Python 3.4, we will talk about what classes are, how do we use them and what can we do with them.
Python is an object-oriented programming language, and as such it has a class mechanism. Classes are extensible templates for creating objects, providing initial values for state (variables) and implementation of behavior (functions and methods).
Compared with other languages, Python’s class mechanism adds classes with a minimum of new syntax. See:
class ClassName: body
The only new thing we need to learn to create a class as simple as this one is the keyword class
, pretty simple. To get an instance of this class, we don’t need to add a new keyword to our dictionary, we just do my_var = ClassName()
, and now my_var
is an instance of ClassName
. Still simple.
Let’s talk about the body of a class. When we define a class, its body is executed, in other words if we write a script that contains only the next piece of code:
simple.py
class MyBodyWillBeExecuted: print("MyBodyWillBeExecuted is being defined")
By running it, like python3 simple.py
, the output will say:
$ python3 simple.py MyBodyWillBeExecuted is being defined
And that is actually how class variables are defined, but before we see an example, let’s see a definition. Class variables are variables that are available at class level, all instances of a class will be able to access these variables. If you come from Java, you can think of them as static variables. Now, let’s see:
simple.py
class MyBodyWillBeExecuted: class_variable = "a value"
Now MyBodyWillBeExecuted
has a class variable, every instance of MyBodyWillBeExecuted
will be able to read class_variable
. Now, as in every other OOP language that I know of, classes in Python have constructors.
A constructor in Python is a function that is executed when an instance of its class is created, and is always called __init__
, let’s see:
simple.py
class MyBodyWillBeExecuted: print("this print is executed when the class is defined") class_variable = "a value" def __init__(self): print("this print is executed when an instance of this class is created") a = MyBodyWillBeExecuted() b = MyBodyWillBeExecuted()
That __init__(self)
is the constructor of our class. This function receives a parameter, self
, we’ll talk about this later. Now, let’s see the output of this script:
$ python3 simple.py this print is executed when the class is defined this print is executed when an instance of this class is created this print is executed when an instance of this class is created
As you see, the “this print is executed when the class is defined” message is printed once when we defined the class, and the “this print is executed when an instance of this class is created” message is printed twice, once for every instance of MyBodyWillBeExecuted
we create.
Let’s talk about that self
argument. In Python, there are no shorthands for referencing the object’s members from its methods/functions, the method/function is defined with an explicit first argument representing the object, which is provided implicitly by the call. This is not specific to the __init__
method, but for all instance methods. This might seem a little weird, but you’ll get the hang of it by practice.
Now that we’ve cleared out that self
argument, we can talk about instance variables. To create a variable in an instance of a class, you need to assign its value to a property of self
. Let’s see:
simple.py
class MyBodyWillBeExecuted: class_variable = "a value" def __init__(self, n): self.n = n a = MyBodyWillBeExecuted(1) b = MyBodyWillBeExecuted(2) print(a.n) print(b.n)
The new script changed the constructor signature, as it now receives an argument n
and assigns it to self.n
. That self.n
will be available in every instance of that class, as we see in the print statements that are printing a.n
and b.n
. The output below.
$ python3 simple.py 1 2
Now, let’s access that variable from another method, called add_one
:
simple.py
class MyBodyWillBeExecuted: class_variable = "a value" def __init__(self, n): self.n = n def add_one(self): self.n += 1 return self.n a = MyBodyWillBeExecuted(1) b = MyBodyWillBeExecuted(2) while a.n < 10: a.add_one() print(a.n) print(b.n)
From another function now, we are accessing the instance variable n
, by doing self.n
. Let’s see the output.
$ python3 simple.py 10 2
One thing that’s worth noticing here, is that we have made all variables and functions public until now… well, that’s actually not optional, Python doesn’t provide a way of making things private. Instead, there is a convention, every name prefixed with an underscore should be treated as a non-public part of the class.
Let’s see an example of this:
simple.py
class MyBodyWillBeExecuted: class_variable = "a value" def __init__(self, n): self.n = n def _add(self, n): self.n += n def add_one(self): self._add(1) return self.n a = MyBodyWillBeExecuted(1) b = MyBodyWillBeExecuted(2) while a.n < 10: a.add_one() b._add(1) print(a.n) print(b.n)
We defined a new method called _add
that receives a number as argument and adds it to self.n
. This method will be available outside of the instance, as you can see when we do b._add(1)
, but you shouldn’t call this method from outside, it’s a convention that almost every Python programmer knows and follows.
Also, Python provides a mechanism to hide things to prevent them from being accessed from outside a class, but let’s make it clear, it is not making anything private. If you name a member of a class with two underscores as prefix, Python will internally change its name to include the class name, this means that if you have an attribute called __private_member
in a class called AClass
, Python will internally rename this attribute _AClass__private_member
and refactor every internal calls, but not external ones. Of course, you can still access this member by doing instance._AClass__private_member
, so it is not actually private. I don’t really do this often, but maybe I should, I just really don’t see the point.
Let’s do some recap. We’ve talked about classes syntax, constructors, class variables and instance variables and instance methods/functions, let’s talk about destructors.
As you might know, Python deletes unreferenced objects automatically to free memory space (garbage collection). This process runs during program execution and it’s triggered when an object’s reference count reaches zero. An object’s reference count increases when it is assigned a new name or placed in a container (list, tuple, or dictionary). The object’s reference count decreases when it’s deleted with del, its reference is reassigned, or its reference goes out of scope. When an object’s reference count reaches zero, Python collects it automatically.
In most cases (integers, strings, list, dictionaries, etc.), you won’t notice when an object is deleted, but a class can implement the __del__
method, which will be invoked right before the instance is destroyed. This method was meant to be used to release non memory resources an instance may use (files, sockets, etc.). Let’s see:
simple.py
class MyBodyWillBeExecuted: class_variable = "a value" def __init__(self, n): self.n = n def _add(self, n): self.n += n def add_one(self): self._add(1) return self.n def __del__(self): print("I'm being destroyed... Goodbye world!") a = MyBodyWillBeExecuted(1) b = MyBodyWillBeExecuted(2) while a.n < 10: a.add_one() b._add(1) print(a.n) a = None print(b.n)
Every instance of this class will print “I’m being destroyed… Goodbye world!” when Python is about to destroy it and reclaim the memory consumed by it. So, let’s predict what is going to happen:
- The class
MyBodyWillBeExecuted
is defined and two instances of it are created (a and b). - The method
a.add_one()
is called untila.n
is 10 andb._add(1)
is executed. - The value of
a.n
is printed, and thenNone
is assigned to a. Now the instance ofMyBodyWillBeExecuted
that was referenced by a is an orphan, no one is pointing to it, so it will be destroyed. - The value of
b.n
is printed. - The program, about to be terminated, will destroy the remaining instance of
MyBodyWillBeExecuted
.
Let’s see the output to check this program behaves as expected:
$ python3 simple.py 10 I'm being destroyed... Goodbye world! 3 I'm being destroyed... Goodbye world!
Now, let’s see a complete example of what we’ve seen here. We’ll write a class User
, which will contain a name
and a password
, we’ll see what to do with it later.
basic.py
... class User: def __init__(self, name, password): self.name = name self.password = password ...
It’s a simple class, with two instance variables, nothing too hard. Now, passwords are supposed to be stored hashed, so, let’s do that. Let’s write a class called Hasher
, that will hold an instance variable salt
and a function hash
.
basic.py
... import hashlib, binascii ... class Hasher: def __init__(self, salt): self._salt = salt def hash(self, value): bin_hash = hashlib.pbkdf2_hmac('sha512', value.encode('utf-8'), self._salt, 100000) return binascii.hexlify(bin_hash).decode('utf-8') ...
The constructor receives a variable called salt
and stores it in an instance variable. The function hash
receives a value, uses pbkdf2
from hashlib
(a module provided by Python) to hash it, and then the module binascii
to translate the returned binary into a hex string. The resulting string is returned.
Now we need a place to store this users, let’s call it UserStorage
. It will store users in a dictionary, indexed by the user’s name, to guarantee that it will be unique.
basic.py
... class UserStorage: def __init__(self): self._store = {} def store(self, user): self._store[user.name] = user def find(self, user_name): return self._store.get(user_name) ...
It isn’t doing anything strange, there is a constructor that initializes an instance variable _store
as an empty dictionary, then there are two functions, one to store an user and another one to retrieve a user by its name. Let’s glue it all together in a GUI (Graphic User Interface).
We’ll write a class GUI
that will print and prompt stuff from the console.
basic.py
... import getpass ... class GUI: def __init__(self, storage, hasher): self._storage = storage self._hasher = hasher def _create_user(self): user_name = input("user name: ") password = self._hasher.hash(getpass.getpass(prompt="password: ")) user = User(user_name, password) self._storage.store(user) def _check_login(self): user_name = input("user name: ") password = self._hasher.hash(getpass.getpass(prompt="password: ")) user = self._storage.find(user_name) if user is None or user.password != password: print("login failed") else: print("login successful") def main_menu(self): print("1. Create User\n2. Check Login\n0. Exit") option = int(input("what do you want to do? ")) if option == 1: self._create_user() elif option == 2: self._check_login() if option != 0: self.main_menu() ...
This class has a constructor that receives a Hasher
and a UserStorage
, and holds them in instance variables. There is one “public” function called main_menu
that will print the options and take action according user’s input, it’s a recursive function that will keep calling itself until the user inputs 0.
If the user inputs 1, main_menu
will call self._create_user
, a “private” method that will ask the user for the user name and password, hash the password, create the User
and store it.
If the user inputs 2, main_menu
will call self._check_login
, another “private” method that will ask the user for credentials and check them with the UserStorage
.
To ask for passwords, this class uses a module called getpass
provided by Python, which is a tool to ask for passwords from the terminal, passwords written in the terminal when prompted with getpass
will not be shown. Let’s see how the whole code looks like.
import hashlib, binascii, getpass class Hasher: def __init__(self, salt): self._salt = salt def hash(self, value): bin_hash = hashlib.pbkdf2_hmac('sha512', value.encode('utf-8'), self._salt, 100000) return binascii.hexlify(bin_hash).decode('utf-8') class User: def __init__(self, name, password): self.name = name self.password = password class UserStorage: def __init__(self): self._store = {} def store(self, user): self._store[user.name] = user def find(self, user_name): return self._store.get(user_name) class GUI: def __init__(self, storage, hasher): self._storage = storage self._hasher = hasher def _create_user(self): user_name = input("user name: ") password = self._hasher.hash(getpass.getpass(prompt="password: ")) user = User(user_name, password) self._storage.store(user) def _check_login(self): user_name = input("user name: ") password = self._hasher.hash(getpass.getpass(prompt="password: ")) user = self._storage.find(user_name) if user is None or user.password != password: print("login failed") else: print("login successful") def main_menu(self): print("1. Create User\n2. Check Login\n0. Exit") option = int(input("what do you want to do? ")) if option == 1: self._create_user() elif option == 2: self._check_login() if option != 0: self.main_menu() if __name__ == '__main__': user_storage = UserStorage() hasher = Hasher(b"some_salt") GUI(user_storage, hasher).main_menu()
The main code initialized both the UserStorage
and the Hasher
and then creates the GUI
and starts the main_menu
. Let’s see the output.
$ python3 basic.py 1. Create User 2. Check Login 0. Exit what do you want to do? 1 user name: svinci password: 1. Create User 2. Check Login 0. Exit what do you want to do? 2 user name: svinci password: login successful 1. Create User 2. Check Login 0. Exit what do you want to do? 2 user name: svinci password: login failed 1. Create User 2. Check Login 0. Exit what do you want to do? 2 user name: asd password: login failed 1. Create User 2. Check Login 0. Exit what do you want to do? 0
This input shows how to create a user, and then tests a successful login, a login with the wrong password and another one with the wrong user name. Let’s jump to the next subject now, which is inheritance.
In Python, as in most OOP languages, you can write classes that inherit from other classes. If you have a class Car
and another class Bus
, you’ll find that they have a lot in common, and that’s because they are both vehicles, so it would be a good idea to make them inherit from a class Vehicle
. Let’s see it in practice.
We’ll write a class Vehicle
that holds the information every vehicle has, no matter if it’s a bus, a car or a motorcycle. Then, this class will define the behavior every vehicle has: accelerator
, breaks
, honk
and speedometer
.
inheritance.py
class Vehicle: def __init__(self, wheels, max_speed, acceleration, breaks_acceleration, brand, model): self.wheels = wheels self.max_speed = max_speed self.acceleration = acceleration self.breaks_acceleration = breaks_acceleration self.current_speed = 0 self.brand = brand self.model = model def accelerator(self, target_speed): while self.current_speed < target_speed and self.current_speed < self.max_speed: self.current_speed += self.acceleration self.speedometer() def breaks(self, target_speed=0): while self.current_speed > 0 and self.current_speed > target_speed: self.current_speed -= self.breaks_acceleration self.speedometer() def speedometer(self): print("{}, {}: {} kmph".format(self.brand, self.model, self.current_speed)) def honk(self): print("HONK!") ...
It’s pretty basic, but it will do the job. As you see, this class is written as any other class we’ve wrote so far, not additional syntax is necessary to be able to inherit from a class. As I’m from Argentina, the speed will be measured in kilometers per hour (kmph). Now, let’s see, having this class, how we can write Car
and Bus
.
inheritance.py
... class Car(Vehicle): def __init__(self): Vehicle.__init__(self, 4, 130, 10, 30, "Ford", "Focus") class Bus(Vehicle): def __init__(self): Vehicle.__init__(self, 4, 80, 5, 50, "Mercedes", "Don't Know any model of bus") ...
Those are some really tiny classes there, but they are powerful enough for us, as they hold all the variables and behavior as a Vehicle
.
As you might see, to inherit from a class we write its name between parentheses between the name of our class and the colon. Then, in the constructor, we need to call our parent class’ constructor giving self
as first argument. Let’s see it all together now.
inheritance.py
class Vehicle: def __init__(self, wheels, max_speed, acceleration, breaks_acceleration, brand, model): self.wheels = wheels self.max_speed = max_speed self.acceleration = acceleration self.breaks_acceleration = breaks_acceleration self.current_speed = 0 self.brand = brand self.model = model def accelerator(self, target_speed): while self.current_speed < target_speed and self.current_speed < self.max_speed: self.current_speed += self.acceleration self.speedometer() def breaks(self, target_speed=0): while self.current_speed > 0 and self.current_speed > target_speed: self.current_speed -= self.breaks_acceleration self.speedometer() def speedometer(self): print("{}, {}: {} kmph".format(self.brand, self.model, self.current_speed)) def honk(self): print("HONK!") class Car(Vehicle): def __init__(self): Vehicle.__init__(self, 4, 130, 10, 30, "Ford", "Focus") class Bus(Vehicle): def __init__(self): Vehicle.__init__(self, 4, 80, 5, 50, "Mercedes", "Don't Know any model of bus") if __name__ == '__main__': bus = Bus() car = Car() car.accelerator(40) bus.accelerator(40) car.breaks(20) bus.honk() car.accelerator(40)
In the main code, we instantiate a Bus
and a Car
, the car then goes at 40 kmph, the bus follows along, the car breaks, the bus honks so the car accelerates again. Let’s see the output.
$ python3 inheritance.py Ford, Focus: 40 kmph Mercedes, Don't Know any model of bus: 40 kmph Ford, Focus: 10 kmph HONK! Ford, Focus: 40 kmph
Great. We’ve got these vehicles working, but wait, what if our bus have some super futuristic breaks that actually have no acceleration, but changes the speed to 0 instantly? Everyone on the bus would probably die, but still, let’s do it. Let’s override the breaks
.
You can always override your parent class methods. One reason for overriding parent’s methods is because you may want special or different functionality in your subclass. In Python no keywords or special syntax is needed, we just re define the method we want to override, see.
inheritance.py
... class Bus(Vehicle): def __init__(self): Vehicle.__init__(self, 4, 80, 5, 50, "Mercedes", "Don't Know any model of bus") def breaks(self, target_speed=0): print("SUPER AWESOME BREAKS!") self.current_speed = target_speed self.speedometer() ...
This bus will break a lot faster than any other vehicle. Let’s use these new breaks by changing our main code.
inheritance.py
... if __name__ == '__main__': bus = Bus() car = Car() car.accelerator(40) bus.accelerator(40) car.breaks(20) bus.honk() car.accelerator(40) bus.breaks(0) ...
The new output can be found below.
$ python3 inheritance.py Ford, Focus: 40 kmph Mercedes, Don't Know any model of bus: 40 kmph Ford, Focus: 10 kmph HONK! Ford, Focus: 40 kmph SUPER AWESOME BREAKS! Mercedes, Don't Know any model of bus: 0 kmph
The behavior remains the same for all methods, except for the breaks of the bus. Now, our car’s accelerator is broken, it adds 10 kmph to the target speed when it’s accelerated. Let’s write that.
inheritance.py
... class Car(Vehicle): def __init__(self): Vehicle.__init__(self, 4, 130, 10, 30, "Ford", "Focus") def accelerator(self, target_speed=0): super().accelerator(target_speed + 10) ...
To call our parent class’ method accelerator
within our child class, we use the built-in function super()
. Let’s see the behavior of this.
$ python3 inheritance.py Ford, Focus: 50 kmph Mercedes, Don't Know any model of bus: 40 kmph Ford, Focus: 20 kmph HONK! Ford, Focus: 50 kmph SUPER AWESOME BREAKS! Mercedes, Don't Know any model of bus: 0 kmph
Now our car is 50 kmph instead of 40 kmph, just as we… wanted? Well, it is what it is, we need to take it to the mechanic.
3. Download the Code Project
This was an example on Classes in Python.
You can download the full source code of this example here: python-classes