In [1]:
class MyFirstClass:
    def __init__(self, name):
        self.name = name

    def greet(self):
        print('Hello {}!'.format(self.name))
In [2]:
my_instance = MyFirstClass('John Doe')
print('my_instance: {}'.format(my_instance))
print('type: {}'.format(type(my_instance)))
print('my_instance.name: {}'.format(my_instance.name))
my_instance: <__main__.MyFirstClass object at 0x106a18588>
type: <class '__main__.MyFirstClass'>
my_instance.name: John Doe

Methods

The functions inside classes are called methods. They are used similarly as functions.

In [3]:
alice = MyFirstClass(name='Alice')
alice.greet()
Hello Alice!

__init__()

__init__() is a special method that is used for initialising instances of the class. It's called when you create an instance of the class.

In [4]:
class Example:
    def __init__(self):
        print('Now we are inside __init__')
        
print('creating instance of Example')
example = Example()
print('instance created')
creating instance of Example
Now we are inside __init__
instance created

__init__() is typically used for initialising instance variables of your class. These can be listed as arguments after self. To be able to access these instance variables later during your instance's lifetime, you have to save them into self. self is the first argument of the methods of your class and it's your access to the instance variables and other methods.

In [5]:
class Example:
    def __init__(self, var1, var2):
        self.first_var = var1
        self.second_var = var2
        
    def print_variables(self):
        print('{} {}'.format(self.first_var, self.second_var))
        
e = Example('abc', 123)
e.print_variables()
    
abc 123

__str__()

__str__() is a special method which is called when an instance of the class is converted to string (e.g. when you want to print the instance). In other words, by defining __str__ method for your class, you can decide what's the printable version of the instances of your class. The method should return a string.

In [6]:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __str__(self):
        return 'Person: {}'.format(self.name)
    
jack = Person('Jack', 82)
print('This is the string presentation of jack: {}'.format(jack))
This is the string presentation of jack: Person: Jack

Class variables vs instance variables

Class variables are shared between all the instances of that class whereas instance variables can hold different values between different instances of that class.

In [7]:
class Example:
    # These are class variables
    name = 'Example class'
    description = 'Just an example of a simple class'

    def __init__(self, var1):
        # This is an instance variable
        self.instance_variable = var1

    def show_info(self):
        info = 'instance_variable: {}, name: {}, description: {}'.format(
            self.instance_variable, Example.name, Example.description)
        print(info)


inst1 = Example('foo')
inst2 = Example('bar')

# name and description have identical values between instances
assert inst1.name == inst2.name == Example.name
assert inst1.description == inst2.description == Example.description

# If you change the value of a class variable, it's changed across all instances
Example.name = 'Modified name'
inst1.show_info()
inst2.show_info()
instance_variable: foo, name: Modified name, description: Just an example of a simple class
instance_variable: bar, name: Modified name, description: Just an example of a simple class

Public vs private

In python there's now strict separation for private/public methods or instance variables. The convention is to start the name of the method or instance variable with underscore if it should be treated as private. Private means that it should not be accessed from outside of the class.

For example, let's consider that we have a Person class which has age as an instance variable. We want that age is not directly accessed (e.g. changed) after the instance is created. In Python, this would be:

In [8]:
class Person:
    def __init__(self, age):
        self._age = age
        
example_person = Person(age=15)
# You can't do this:
# print(example_person.age)
# Nor this:
# example_person.age = 16

If you want the age to be readable but not writable, you can use property:

In [9]:
class Person:
    def __init__(self, age):
        self._age = age
        
    @property
    def age(self):
        return self._age
        
example_person = Person(age=15)
# Now you can do this:
print(example_person.age)
# But not this:
#example_person.age = 16
15

This way you can have a controlled access to the instance variables of your class:

In [10]:
class Person:
    def __init__(self, age):
        self._age = age
        
    @property
    def age(self):
        return self._age
    
    def celebrate_birthday(self):
        self._age += 1
        print('Happy bday for {} years old!'.format(self._age))
        
example_person = Person(age=15)
example_person.celebrate_birthday()
Happy bday for 16 years old!

Introduction to inheritance

In [11]:
class Animal:
    def greet(self):
        print('Hello, I am an animal')

    @property
    def favorite_food(self):
        return 'beef'


class Dog(Animal):
    def greet(self):
        print('wof wof')


class Cat(Animal):
    @property
    def favorite_food(self):
        return 'fish'
In [12]:
dog = Dog()
dog.greet()
print("Dog's favorite food is {}".format(dog.favorite_food))

cat = Cat()
cat.greet()
print("Cat's favorite food is {}".format(cat.favorite_food))
wof wof
Dog's favorite food is beef
Hello, I am an animal
Cat's favorite food is fish