Object programming¶
Object oriented programming¶
Object-oriented Programming
, or OOP
for short, is a programming paradigm, which provides a means of structuring programs based on the properties and behaviuor of individual objects.
People always prefer to use something what is known and familiar, that's why the high level concept was created - on objects, that represent real things (animal, human, car) instead of 0 and 1 (which are familiar for computer).
For instance, an object could represent a person with a name property, name, age, address, etc., with behaviours like walking, talking, breathing, and running. Or an email with properties like recipient list, subject, body, etc., and behaviours like adding attachments and sending.
Put another way, object-oriented programming is an approach for modelling concrete, real-world things like cars as well as relations between things like companies and employees, students and teachers, etc. OOP models real-world entities as software objects, which have some data associated with them and can perform certain functions.
Another common programming paradigm is procedural programming which structures a program like a recipe in that it provides a set of steps, in the form of functions and code blocks (if, loop, etc.), which flow sequentially in order to complete a task.
In object oriented programming data (objects) and operation on them are at center, instead of functions and algorithms.
Objects are represented by attributes (as variable) and methods (functions defined for object and called on object), which can read and modify an object attribute.
Class and object¶
So far we've got to know data types like numbers, string, list, which are designed to represent simple things: the variable with the data type int
or float
can represent some product price or some person height; value str
allow to store the name of favou rite animal or household address; list are used to store last lottery draw results.
But what if we want to present something more complex? In such a case primitive types might be not enough.
For example we want to have a student's diary. If we would use a list, first element could be the name of student, the second his surname. In this case we would create variable, which number of elements is twice the number of students.
journal = ['Ann', 'Scot', 'John', 'Smith', 'Mat', 'Simson']
student_1 = journal[0] + ' ' + journal[1]
student_2 = journal[2] + ' ' + journal[3]
student_3 = journal[4] + ' ' + journal[5]
print(len(journal)) # Return 6
Such a structure will not cause the issue at the beginning. However the management of such a list will not be efficient, if they get bigger (class with 40 students? why not?). What, if during work on the program, we will decide, that we need to add date of birth, grades, height or weight? We will get to no organized data.
Classes are used to create new user-defined data structures. In case of student it would be recommended to create class Student()
, to track his attributes (properties) like name, surname, age, class, favourite subjects, grades, height, weight and actions (methods), that specific student can do: learning, go to break, write tests, go to school shop etc.
It’s important to note, that a class just provides structure—it’s a blueprint for how something should be defined. Class is a recipe how to create some representation, something that has all attributes defined in class. This is an object.
While the class is the blueprint, an instance is a copy of the class with actual values, literally an object belonging to a specific class. It’s not an idea anymore; it’s an actual student, from class 3a, his favourite lessons are gym and his name is John.
Summary:
Class is the blueprint, where object is defined, it describes values, methods and default state.
Object is instance of a class, It has values and methods, which are defined in class from which it comes. You can infinity the number of objects from the class in program.
Class definition¶
- Class is created by using a key word class.
# Simplest, nothing doing class
class Student:
pass
- Class (with attributes and methods) create a code block, so we should use cut ins.
- __init__(self) it's a specific method (function), used every time, when we create the object for common class; method __init__ is called an initializer.
class Student:
def __init__(self):
pass
- Parameter self is required as a first parameter in methods, it makes method relate to object, on which they are used.
class Student:
def __init__(self):
self.name = "John"
self.grade = 4
Below, we have example of Animal
class, which will be used to create object, that represents animals. Class contains:
- class variable :
SPECIES
- as it is "class", values of this variable is set in class, so it's shared with all objects (every object uses the same value, changing its value in one object causes its modification in another). - initializer
__init__
it contains operations, that are done, when object is created - here, object variables are set, each instance has their own copy of variables and changing its value in one object causes its modification in another, - function definition
print_details(self)
, as it was defined in the class we call it method.
class Animal:
SPECIES = "" # class variable
def __init__(self):
self.name = "Johnny" # default value for variable name from class Animal
self.age = 2
def print_details(self): # method, that displays object state
print(f"Name: {self.name}, age: {self.age}.")
Instantiating Objects¶
- Object can be created after class is defined. It's similar to how we get the result from function to variable.
snoopy = Animal()
- Instantiating object execute method __init__ from class. In a simple way we can say, that class call is using function that's defined in class
__init__(self)
. - To get variable value or execute method on object we need to use dot operated ..
class Animal:
...
my_dog = Animal() # here we create object, __init__ initializer called
my_dog.print_details() # method print_details() executed on object my_dog
print(my_dog.name) # get to variable name from object my_dog
my_dog.age = 3 # age variable update in my_dog object
Every object has its state. When we change variable of one object, it not change others. The following example is demonstrated an object independence:
class Animal:
...
puppy = Animal()
dog = Animal() # create second object of Animal class
puppy.age = 1
puppy.name = "Rex Junior"
dog.age = 10
dog.name = "Rex Senior"
print(f"My dog: {puppy.name}, {puppy.age} and older dog: {dog.name}, {dog.age}")
Initializer __init__¶
Instead of set default values for variables in object, we can set them in the initializer, when object is created.
class Animal:
def __init__(self, name="Rex", age=2):
self.name = name
self.age = age
my_cat = Animal("Snoopy", 5)
my_parrot = Animal("Ara") # age set to 2 by default
my_turtle = Animal() # Default name and age value
All class arguments used in create object function, are forwarded to the initializer. The instance of this object will set variables based on __init__
method.
Basic magic methods¶
The methods that magic in Python are the ones whose names begin and end with a double floor, i.e. "__". In general, the __init__ constructor mentioned above is one such method. The purpose of the existence of magic methods is to make our classes able to smoothly enter the Python ecosystem, i.e. be comparable, sortable or, for example, be able to cope if we pass them to functions such as len or str.
__str__ and __repr__¶
If we execute the following code fragment:
class Animal:
def __init__(self, name="Rex", age=2):
self.name = name
self.age = age
print(Animal())
then we will get a result similar to
<__main__.Animal object at 0x7f885ead6690>
However, if we add the method __str__ to our class:
class Animal:
def __init__(self, name="Rex", age=2):
self.name = name
self.age = age
def __str__(self):
return f"<{self.name} ({self.age})>"
print(Animal())
<Rex (2)>
The handling of __repr__ functions is similar - the main difference between them is the purpose. __str__ is used when creating the human representation of an object and __repr__ is used when creating an object representation for the computer.
Compare¶
Set of comparison operators and their corresponding magic methods:
< and corresponding method:__lt__
<= and corresponding method:__le__
== and corresponding method:__eq__
!= and corresponding method:__ne__
>= and corresponding method:__ge__
> and corresponding method:__gt__
And example:
class Animal:
def __init__(self, name="Rex", age=2):
self.name = name
self.age = age
def __gt__(self, other):
return self.age > other.age
print(Animal("Alex", 3) > Animal("Milk", 2))
in which, by defining the __gt__ method, we can compare animals by age. The result of this code is True.
Length¶
So method __len__. Defining it allows us to use our classes as arguments to the len function:
class Animal:
def __init__(self, name="Rex", age=2):
self.name = name
self.age = age
def __len__(self):
return self.age
print(len(Animal("Alex", 3)))
In this example, we have defined that we specify the length of our object as the value of the age field. The result of executing the code is 3.
Mathematical operators and casting to other types¶
We can make it possible to perform mathematical operations on instances of our classes. Both add between each other and between different types. Methods such as:
__add__ operator: +
__sub__ operator: -
__mul__ operator: *
__floordiv__ operator: //
__truediv__ operator: /
__mod__ operator: %
__pow__ operator: **
__lshift__ operator: <<
__rshift__ operator: >>
__and__ operator: &
__xor__ operator: ^
__or__ operator: |
We can also teach our classes to be projected to eg an int. The following methods are used for this:
int() __int__
long() __long__
float() __float__
oct() __oct__
hex() __hex__
An example could look like this:
class Animal:
def __init__(self, name="Rex", age=2):
self.name = name
self.age = age
def __add__(self, other):
return Animal(self.name, self.age + int(other))
def __str__(self):
return f"<{self.name} ({self.age})>"
def __int__(self):
return self.age
print(Animal("Alex", 1) + 1)
print(Animal("Alex", 3) + Animal("Milk", 2))
where we defined that if we add something to the Animal class, the result will be a new class but its "age" field will be increased accordingly. The result of this code is:
<Alex (2)>
<Alex (5)>
There are at least a dozen more magical methods, but they all have the same goal - to make our classes take full advantage of the possibilities of the Python language. This is a really powerful tool.
Private and public variables¶
In Animal class we could get to every variable and update it - also everyone could do this. It's not always recommended (what, if someone set age to -10 by mistake?).
For more control on objects, Python allow to define variables as private and public, which limit access to them.
Usage of _ in variable name suggests other programmers, that variable can be updated only by method of this object. It's only suggestion - change of variable value still can be updated without methods of this class. Such a variable is called protected.
class Animal:
def __init__(self, name="Rex", age=2):
self._name = name
self._age = age
my_dog = Animal()
print(my_dog._name) # it works, will print value of name variable, from my_dog object
- To set real value and variable privacy, you need to add __ to object variable name. In this case variable value set will not be possible without class methods usage - otherwise, Python will return error
AttributeError
(it's treated as such attribute not exist, it's hidden). Such variables are called private.
class Animal:
def __init__(self, name="Rex", age=2):
self.__name = name
self.__age = age
my_dog = Animal()
print(my_dog.__name) # Python return error!!!
How to set privet variable value:
class Animal:
def __init__(self, name="Rex", age=2):
self.__name = name
self.__age = age
def set_age(self, age):
if age > 0:
self.__age = age
else:
print("Age must be grater than 0.")
def get_age(self):
return self.__age
my_dog = Animal()
my_dog.set_age(3)
print(my_dog.get_age()) # Will show updated __age variable value from my_dog object
Properties¶
In above example we prevent user to cheat us and set negative number for age, we use separated method to show attribute and other to set it.
Instead of methods like set_age
or get_age
, we can use so called property
(specific attribute), that help encapsulate variables in more python way. It's more python way for better and safer attribute management without additional methods definition.
property
it might have methods getter, setter and deleter.- The name of method, that use a property mechanism for operations get, set and delete, needs to be the same!
class Animal:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property # getter - get variable value
def age(self):
return self.=__age
@age.setter # setter - set variable new value
def age(self, age):
if age > 0:
self.=__age = age
else:
print("Age must be greater than 0.")
@age.deleter # deleter - remove variable
def age(self):
del self.=__age
my_dog = Animal()
my_dog.age = 3 # Set age - using setter
print(my_dog.age) # read age - using getter
del my_dog.age # Remove variable - using deleter
property
is defined by name, that used to access hidden attribute.- Next create method with chosen name from previous step and add command
@property
, encapsulate method causing we have a specific attribute. - Command
@property
in method definition allows to set getter, method that will be used every time, when someone will read attribute value. - Command
@<method_name_from_property.setter>
allows to set action in case of attribute update - here we can see several checks - is value have valid type or is from expected range. - Method with
@<method_name_from_property.deleter>
is that will call when attribute deleted (i.e. commanddel obiekt.attribute_name
). - Command
@property
uses decorate mechanism in Python. More in next part of this course.
Value and reference¶
When we assign an object to a variable, we create the relation called reference. If many variables are related to the same object, we can modify it using any of them. We can object state using one variable, and it will be propagated in other variables, that set to this object. This way we can use objects as function arguments, other objects methods or move between files and still we will use one same instance, with it values and same methods.
class Animal:
def __init__(self, name="Rex", age=2):
self.__name = name
self.__age = age
dog_a = Animal()
dog_b = dog_a
print(my_dog_a.name) # Return "Rex"
print(dog_b.name) # Return "Rex"
dog_a.name = "Pongo"
print(my_dog_a.name) # Return "Pongo"
print(dog_b.name) # Return "Pongo"
Object-oriented programming paradigms¶
There are four basis rules in object-oriented programming, that define root of this way of programming. There are:
- Abstraction
- Encapsulation
- Inheritance
- Polymorphism
Abstraction¶
Abstraction means concept or idea, where it's not related with specific instance. It's difficult to imagine an animal or a fruit - always we will think about some specific, existed object: a dog, a cat, a mouse or a banana. In this case we don't want to get specific animal or fruit, but only the meaning, that abstract concept of an animal or a fruit is not imaginable.
In the same way in object-oriented programming - in this concept the class is often defined, instead of implementation itself. This way we create base, that features can be extended.
Encapsulation¶
Encapsulation is a mechanism to hide data implementation by limit only to public methods. Instance variables are defined as private and access methods are public and they are used to update private variable values. This is called encapsulation.
Python is specific language, because it offers huge freedom - without explicit property
mechanism use or private variables feature use, default variable values - variables and methods are available from each module or other class object etc.
Inheritance¶
Inheritance allows child class to use methods and attributes from base (parent) class. Child class automatically gets access to the same variables and methods, that's in parent class. We don't need to define those variables and methods again, this way we safe time. Also, when we want to create class, that is similar to other, we don't need to re-write or copy the same code - we just need to inherit of older class.
Inheritance expresses relation is. For example dog is an animal, banana is a fruit. Based on this relation, class Banana
looks like perfect candidate to get class Fruit
functionality.
Inheritance is defined by parent class name in brackets, right after child class name.
# Parent class
class Vehicle:
pass
# Child class
class Car(Vehicle):
pass
Here is more real inheritance example:
class Human:
def __init__(self, name, height, weigth):
self.name = name
self.height = height
self.weigth = weigth
class Programist(Human):
def __init__(self, name, height, weigth, languages):
super().__init__(name, height, weigth)
self.languages = languages
bob = Programist("Bob", 180, 100, ["Python", "Java"])
print(bob.name) # Access to inherited Human class name attribute
Programist is a human, so such a relation is make sense and objects from Programist
class can inherit Human
class attributes.
In specific cases we might need to implement class, that inherits from more than one parent class.
class Bat:
pass
class Man:
pass
class Batman(Bat, Man):
pass
In this case Batman
class will have access to all attributes and methods from Man
and Bat
classes.
Polymorphism¶
Polymorphism is "various", means one name/object/method and many ways of usage. We get by overwrite methods. Child class inherits methods for its parent, can re-define them and use in itself way. Polymorphism is strongly related to inheritance. We can crate code, that will work in parent class (over type) and it will work in every child class (under type), because both child and parent class, will have access to the same methods.
class A:
def __init__(self):
pass
def print_value(self):
print("some value")
class B(A):
def __init__(self):
pass
def print_value(self):
print("other value")
values = [A(), B()]
for value in values:
value.print_value() # First will display 'some value', and next 'other value'
List values
contains two objects: first A
type, second B
type (in this case objects are not assigned to variables). Thanks to inheritance and that both objects have access to print_value
method, for
loop don't need to check value
variable data type. Only important thing is that both elements provide print_value
method.