Object-oriented Programming in Python

Object-oriented Programming in Python

Everything is Object

In Python, everything is an object. Here is a classfication of Python objects:

python_object_type

We know that in Java there are 8 primitive types(int, boolean, char, etc), which are not object. However, even an integer in Python is an object. For example:

python_int

From the example we can know that a and b have the same reference. a and b are both object of int, and we can call them instance object. int is a type object, and we can interpret it as a class in Java(though it is a class object):

python_int2

There is a special type object named object, and we can interpret it as Object in Java, which means it is the base class:

python_object

We can also define our own class, which is also a type object:

1
2
3
class Student(object):
def study(self): # "self" represents current instance object, which is similar to "this" in Java. And it cannot be omitted.
print("Studying...")

python_student

And we can add attributes to an instance object of Student directly. For example, we can add an attribute name to the instance object student:

python_student2

Magic Methods in Class

There are four magic methods worth noting in a class:

  1. __new__: Create an instance object from a class object.
  2. __init__: Initialize the attributes of the instance object. This method is invoked after the __new__ method.
  3. __str__ : Similar to toString method in Java.
  4. __del__: This method is invoked when an instance object is garbage collected, which happens at some point after all references to the object have been deleted.

Firstly we can figure out the execution order of these methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Student(object):
def __init__(self, name, age):
print("__init__ is invoked.")
self.name = name
self.age = age

def __new__(cls, *args, **kwargs):
print("__new__ is invoked.")
return object.__new__(cls)

def __str__(self):
print("__str__ is invoked.")
return "name:%s | age:%s" % (self.name, self.age)

def __del__(self):
print("__del__ is invoked.")


student = Student("Lily", 16)
print(student)

invoke_order

Encapsulation

Just like Jave, now we want to have private attributes and their getters/setters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Student(object):
def __init__(self, name, age):
self.__name = name # __name is a private attribute.
self.__age = age # __age is a private attribute.

def get_name(self):
return self.__name

def get_age(self):
return self.__age

def set_name(self, name):
self.__name = name

def set_age(self, age):
self.__age = age

def __str__(self):
return "name:%s | age:%s" % (self.__name, self.__age)

student = Student("Lily", 16)
print(student.get_name())
print(student.get_age())

student.set_name("Martin")
student.set_age(18)
print(student.get_name())
print(student.get_age())

python_encapsulation

And we can also have some private methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Student(object):
def __init__(self, name, age):
self.__name = name
self.__age = age

def __read_novels(self): # __read_novels is a private method.
print("Reading novels...")

def read(self): # read is a public method.
self.__read_novels()

def get_name(self):
return self.__name

def get_age(self):
return self.__age

def set_name(self, name):
self.__name = name

def set_age(self, age):
self.__age = age

def __str__(self):
return "name:%s | age:%s" % (self.__name, self.__age)

student = Student("Lily", 16)
student.read()

python_encapsulation2

Inheritance

Single Inheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Car(object):
def __init__(self, brand, color):
self.__brand = brand
self.__color = color

def run(self):
print("%s is running." % self.get_brand())

def get_brand(self):
return self.__brand

def get_color(self):
return self.__color

def set_brand(self, brand):
self.__brand = brand

def set_color(self, color):
self.__color = color

def __str__(self):
return "brand:%s | color:%s" % (self.__brand, self.__color)


# SportsCar is a subclass of Car.
class SportsCar(Car):
def __init__(self, brand, color, speed):
super().__init__(brand, color)
# two other ways to invoke superclass's method:
# Car.__init__(self, brand, color)
# super(BMW, self).__init__(brand, color)
self.__speed = speed

def run(self): # Override run method.
print("%s is running at the speed of %.2f." % (self.get_brand(), self.get_speed()))

def get_speed(self):
return self.__speed

def set_speed(self, speed):
self.__speed = speed

def __str__(self): # Override __str__ method.
return "brand:%s | color:%s | speed:%.2f" % (self.get_brand(), self.get_color(), self.get_speed())

bmw = SportsCar("BMW", "Red", 150)
bmw.run()

single_inheritance

Multiple Inheritance

Python allows a class object to inherit from mupltiple classes, i.e., multiple inheritance. Here is a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A(object):
def foo(self):
print("foo from A")

class B(object):
def foo(self):
print("foo from B")

class C(A,B):
pass

c = C()
c.foo()

multiple_inheritance

From the above example we can find that by default c.foo() invoke the foo method from superclass A. But why? To understand the reason, we can use __mro__ (method resoultion order)
method, which returns a tuple:

multiple_inheritance2

In multiple inheritance, when we search for an attribute in a class that is involved, an order is followed. First, it is searched in the current class. If not found, the search moves to super classes. In the above case, the search order will be left-to-right, i.e. C, A, B, object.
Therefore, if we use c.foo(), the foo method in A will be used.

If we want to invoke B‘s foo method when using c.foo(), we can override the foo method in C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A(object):
def foo(self):
print("foo from A")

class B(object):
def foo(self):
print("foo from B")

class C(A,B):
def foo(self):
B.foo(self)

c = C()
c.foo()

multiple_inheritance3

Static Variables and Methods

All variables defined on the class level in Python are considered static. For example:

1
2
3
4
5
6
7
8
9
10
11
12
class Person(object):
name = "Person" # Belong to class object, which is similar to static vairable in Java.

def __init__(self, name, age):
self.name = name # Belong to instance object.
self.age = age

person = Person("Nora", 23)
print(person.name)
print(Person.name)
Person.name="Someone"
print(Person.name)

python_static

In Python, there are two ways of defining static methods within a class. One way is using @classmethod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person(object):
name = "Person"

"""
1. Similar to static method in Java, both instance object and class object can invoke this change_name method.
2. We can also use class method to modify class attribute, in this case, name.
"""
@classmethod
def change_name(cls, name): # cls means this method is belong to class object.
cls.name = name


person = Person()
print(Person.name)

# Both instance object and class object can invoke this change_name method:
Person.change_name("Someone")
print(Person.name)
person.change_name("Person")
print(Person.name)

python_static2

Another way of defining static methods is using @staticmethod, which cannot modify static varibles, and we can omit this argument cls in these methods:

1
2
3
4
5
6
7
8
9
class Person(object): 
"""
1. Similar to static method in Java, both instance object and class object can invoke this eat method.
2. We cannot use this method to modify class attribute.
3. There is no mandatory argument "cls".
"""
@staticmethod
def eat():
print("Eating...")