Objects are structures which contain data (attributes) and code (hold by methods). In Python everything is an object, so, objects can contain other objects and methods.
In [1]:
class A:
pass
The previous code is identical to:
In [2]:
class A(object):
pass
because all objects inherits from the Object object.
In [3]:
class A():
def Im_a_method(self):
print("Hi!")
self.__Im_a_hidden_method()
def __Im_a_hidden_method(self):
print("you shouln't call me outside this class")
In [6]:
x = A()
print(isinstance(x,A))
In [8]:
s = "hola"
isinstance(s, str)
Out[8]:
In [27]:
x.Im_a_method()
In [28]:
x.__Im_a_hidden_method()
To know the class of an object:
In [ ]:
x.__class__
Remember! In Python everything is an object:
In [ ]:
10.0.__class__
Class attributes (variables and methods) are accesible even if the class is not instantiated.
Because class variables do no depends on any instance, they can be used, for example, to share data between instances:
In [ ]:
class A:
class_variable = 1
A.class_variable
In [ ]:
A.class_variable = 2
A.class_variable
In [ ]:
a = A()
a.class_variable
In [ ]:
b = A()
b.class_variable
By default, (instance) methods are created when the class is instantiated. On the other hand, class methods are created when the class is defined. In order to distinguish them, class methods must be decorated with @classmethod or @staticmethod. The only difference between both types of methods is that in a @classmethod, the name of the class is passed automatically by the interpreter as the first argument.
In [9]:
class A:
def instance_method(self, x):
print('executing instance method with args (%s,%s)' % (self, x))
@classmethod
def class_method(cls, x):
print('executing class method class_method with args (%s,%s)' % (cls, x))
@staticmethod
def static_method(x):
print('executing static (class) method static_method with arg (%s)' % x)
In [10]:
A.instance_method(1)
In [ ]:
A().instance_method(1)
In [ ]:
A.class_method(1)
In [ ]:
A.static_method(1)
Instance attributes (methods and variables) are only accesible when the class has been instantiated. Of the instance methods, one of the most important is the constructor of the class. All instance methods must include a parameter which holds the data instantiated. By convention, this parameter is called self.
In [2]:
class A:
def __init__(self):
print('Constructor called')
def setter(self, x=1):
self.x = x
def getter(self):
return self.x
a = A()
In [3]:
a.setter(2)
print(a.getter())
In [7]:
hasattr(A, 'setter')
Out[7]:
Objects store the instance variables in a dictionary:
In [ ]:
x.__dict__
We can create a new entry in the (class) dictionary (a new instance variable) with:
In [ ]:
x.b = 1
x.b
In [ ]:
x.__dict__
Extends functionality of a (previously defined) class.
In [44]:
class Class_A:
'''Base class A'''
def method_1(self):
self.x = 1
print('Class_A.method_1 called')
class Class_B(Class_A): # Notice that Class_B depends on (inherits from) Class_A
'''Derived class B'''
def method_1(self):
print('Class_B.method_1 called')
# This method extends "Class_A"
def method_2(self):
Class_A.x = 2 # "Class_A" is the prefix of "x" in "Class_B" for "Class_A.x"
print('Class_B.method_2 called')
Class_A.method_1(self)
super().method_1() # This is identical to the previous
self.method_1()
In [45]:
Class_A().method_1()
In [46]:
Class_B().method_2()
In [ ]:
Class_B().method_1()
In [ ]:
print(Class_A.x)
In [ ]:
class Class_A:
'''Base class A'''
def method_1(self):
print('Class_A.method_1 called')
class Class_B:
'''Base class B'''
def method_1(self):
print('Class_B.method_1 called')
class Class_C(Class_A, Class_B): # Class_C inherits from Class_A and Class_B
'''Derived class C'''
def method_1(self):
Class_A.method_1(self)
Class_B.method_1(self)
In [ ]:
Class_C().method_1()
In [ ]:
class Point():
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, point):
'''Overloads "+"'''
return Point(self.x + point.x, self.y + point.y)
def __str__(self):
'''Overloads print()'''
return '(' + str(self.x) + ',' + str(self.y) + ')'
In [ ]:
a = Point(1,2)
b = Point(3,4)
print(a+b)
Python does not define a special syntax for declaring methods that should be implemented in a child class. However, an "abstract" method can be implemented in the "abstract" class as:
In [ ]:
class A():
def do(self):
raise NotImplementedError
x = A()
x.do()
In [ ]:
class B(A):
def do(self):
print('Do something useful')
x = B()
x.do()
In [ ]:
class Stack:
def __init__(self):
self.items = []
def put(self, item):
self.items.append(item)
def get(self):
return self.items.pop()
def size(self):
return len(self.items)
In [ ]:
class Queue:
def __init__(self):
self.items = []
def put(self, item):
self.items.insert(0,item)
def get(self):
return self.items.pop()
def size(self):
return len(self.items)
Use the OPP paradigm to create a shorter implementation of both data structures.