OOP in Python

Object-oriented Programming Paradigm

Object-oriented Programming Paradigm

A step over Procedural Programming

In procedural-languages (C, Fortran, Pascal) the data structures are separated from algorithms of the software entities (a step over Assembly languages)
But in big projects (consider CRM) it is:
difficult to describe the problem solely in terms of procedures.
more difficult to trace which function manipulates which data.

The benefits

A higher level of abstraction for solving real-life problems.
The software system can think in therms of objects, communicating with each other.
Objects are reusable software components, as each encapsulates the data and logic for its operation. A black-box which do what it has to do.

OOP vs Procedural Paradigms

OOP - Main Concepts

OOP - Main Concepts

Class

A class is a blueprint, template, or prototype that defines and describes objects attributes (data) and behaviour(methods)
Can be seen as a user-defined data type

Object

An instance of a class - an entity of given class, that can be used in a program
Instances of same class have similar properties and behaviour

Diagram

Inheritance

A class can inherit attributes and methods from another base class, and at the same time to define its own.

Classes and Objects in Python

Classes and Objects in Python

Minimal Class definition

The minimal class definition in Python looks like:


			class ClassName:
			    pass
		

			class Person:
				pass
		

Object Creation

In python, it's simple:


			object_name = ClassName()
		

			# create objects of class Person:
			pesho = Person()
			maria = Person()

			# let's check:
			print( type(pesho) )
			print( type(maria) )
			# <class '__main__.Person'>
			# <class '__main__.Person'>
		

Objects created from the same class have same type, but they are different entities:


			class Person:
				pass

			maria = Person()
			pesho = Person()

			print( maria == pesho)
			#False
		

Attributes

Attributes

Overview

The attributes describes an object's characteristics.
Usually, objects from same class, have same attributes, but with different values.
In python, we can dynamically create arbitrary new attributes for existing objects

Accessing Object's Attributes - the dot notation


			# write in object attribute:
			object_name.attribute = "value of any type"

			# read from attribute:
			var = object_name.attribute
		

			maria = Person()

			# write in object attribute:
			maria.name = "Maria"
			print(maria.name)

			# read from attribute:
			mn = maria.name
			print(mn)
		

As we'll see later, this is not the proper way to create object's attributes in Python. This is an example of dot notation, used to access object's attributes

Attributes - internal

Attributes are stored internally in a dictionary structure
Each object has associated dictionary attribute, named __dict__, which store an object's (writeable) attributes

			class Person:
				pass

			maria = Person()

			# set attribute:
			maria.name = "Maria"
			maria.age = 100

			print(maria.__dict__)
			# {'name': 'Maria', 'age': 100}
		

Class Attributes

You can define Class Attributes, which will be shared across all class instances (the objects)

			class Person:
				name = "Anonymous"
				age = 100

			maria = Person()
			pesho = Person()

			print(maria.name, maria.age)
			print(pesho.name, pesho.age)

			# Anonymous 100
			# Anonymous 100
		

Methods

Methods

Overview

A method is a function that "belongs to" an object.

Syntax


			class ClassName:
				def method_name(self):
					pass
		
Note, that you must define at least one parameter in the method.
It will take (automatically) a reference to the object, which called that method.
You can name the parameter as you wish, but the convention is to name it self

Syntax - example


			class Person:
				def greet(self):
					print("Hi there! I'm", self.name)

			# create some objects of class Person:
			maria = Person()
			maria.name = "Maria Popova"
			pesho = Person()
			pesho.name = "Pesho"

			# call greet() method on maria. Python will send the maria object reference to the self parameter.
			maria.greet()
			# call greet() method on pesho
			pesho.greet()

			# OUTPUT:
			# Hi there! I'm Maria Popova
			# Hi there! I'm Pesho
		

more on self:

When a method is invoked from an object, Python automatically passes the object reference to the first parameter in the method definition.


			class TestSelf:
				def test(self, obj):
					print(self == obj)

			obj1 = TestSelf()

			# lets check if obj1 == self
			obj1.test( obj1 )

			# True
		

Class Constructor

the __init__() method

__init__() method

What is a Class Constructor

Class Constructor is a method, called automatically when each new object is created.
A class constructor defines the action which will happens when a new object instance is created.
Usually, this actions include setting an initial value for the object attributes.

Syntax

In Python, the Class Constructor is implemented by a special method named __init__()

			class ClassName:
				def __init__(self):
					pass
		

Example 1


			# class definition
			class ClassA:
				def __init__(self):
					print("An object of ClassA is created!")

			# objects creation:
			obj1 = ClassA()
			obj2 = ClassA()

			# output:
			# An object of ClassA is created!
			# An object of ClassA is created!
		

Person Class Constructor Example


			class Person:
				def __init__(self, name, age):
					self.name = name
					self.age = age

				def greet(self):
					print("Hi there! I'm {}, {} years old!".
						format(self.name, self.age))

			maria = Person("Maria Popova", 25)
			pesho = Person("Pesho", 27)

			maria.greet()
			pesho.greet()

			# Hi there! I'm Maria Popova, 25 years old!
			# Hi there! I'm Pesho, 27 years old!

		

Magic (dunder) methods

Magic (dunder) methods

Besides the __init__() methods in Python, there are many other predefined special methods, also called magic methods, which have the same notation form:

			__magic__()
		

The double underscore is often called dunder, thus the methods - dunder methods

__str__ method

Called by str(object), format() and print() built-in functions to compute the “informal” or nicely printable string representation of an object.
You can define inside a class the __str__() method which will be used, when you call the str(), format() or print() functions of a class instance
The return value must be a string!

example


			class Person:
				def __init__(self, name, age):
					self.name = name
					self.age = age

				def __str__(self):
					return "name = {}\nage = {}\n".format(self.name, self.age)

			maria = Person("Maria Popova", 25)

			print(maria)
			# name = Maria Popova
			# age = 25
		

__repr__ method

Called by the repr() built-in function, which is titypically used for debugging
The return value must be a string!
if a class has only the __repr__ method and no __str__ method, __repr__ will be applied in the situations, where __str__would be applied

example


			class Person:
				def __init__(self, name, age):
					self.name = name
					self.age = age

				def __repr__(self):
					return """# This is representation of an object:
					obj = Person()
					obj.name = {}
					obj.age = {}""".format(self.name, self.age)

			maria = Person("Maria Popova", 25)

			print(maria)
		

Note, that the __repr__ method will be invoked, when print(maria) is executed!

__str__ vs __repr__

Use __str__, if the output should be for the end user or in other words, if it should be nicely printed.
Use __repr__ for the internal representation of an object. The output of __repr__ should be - if feasible - a string which can be parsed by the python interpreter. The result of this parsing is in an equal object.

Other dunder methods

Magic Methods and Operator Overloading

Exercises on Class Constructor and dunder methods

Exercises on Class Constructor and dunder methods

The BankAccount Class

Create a BankAccount class, and two objects, as given in the template code

			class BankAccount():
				pass

			maria_account = BankAccount("Maria", 1_300)
			pesho_account = BankAccount("Pesho", 100)

			print(maria_account)
			print(maria_account)

			# OUTPUT:
			#BankAccount:
			# owner = Maria
			# balance = 1300
			#BankAccount:
			# owner = Pesho
			# balance = 100
		

Encapsulation and Data Hiding

Encapsulation and Data Hiding

Python's Way - Conventions

Naming Type Meaning
name Public Attributes, that can be freely used inside or outside of a class definition.
_name Protected Protected attributes should not be used outside of the class definition, unless inside of a subclass definition.
__name Private This kind of attribute should be inaccessible and invisible. It's neither possible to read nor write to those attributes, except inside of the class definition itself.

Private Attributes

Python did not provide a truly private attributes!

The dunder attributes are just prefixed with the _ClassName


			class Person:
				def __init__(self, name, age):
					self.name = name
					self.__age = age

				def __str__(self):
					return "name = {}; __age = {}".format(self.name, self.__age)

			maria = Person("Maria Popova", 25)

			# let's try to change Maria's age:
			maria.__age = 100
			print("maria.__age is set to ", maria.__age)
			print(maria)

			# but __age is not hidden! Look:
			maria._Person__age = 100
			print("maria.__age is set to ", maria.__age)
			print(maria)
		

Inspecting Classes and Objects

Inspecting Classes and Objects

__dict__ - revealing object's dictionary


			class Person:
				def __init__(self, name, age):
					self.name = name
					self.__age = age

			maria = Person("Maria Popova", 25)

			# inspect object attributes dictionary:
			print( maria.__dict__ )

			# inspect class attributes dictionary:
			print( Person.__dict__ )
		

dir() - revealing all object's attributes

The built-in function dir() used with an object name as argument will reveal most of the attributes defined for that object. Check dir() on python3 docs for more information.

			class Person:
				def __init__(self, name, age):
					self.name = name
					self.__age = age

			maria = Person("Maria Popova", 25)

			print("maria has next attributes:")
			print( dir(maria) )

			# of course, dir() is calling the __dir__() method on the object, but it's better to use the function call, though you can do that:
			print( maria.__dir__() )
		

Resources

YouTube Videos

Python Classes and Objects - John Philip Jones

blogs, tutorials

Object-Oriented Programming www.python-course.eu

Exercises

Task1: students_objects.py

The Task

Define a class Student with method __init__(), such that the creation of objects from Student Class looks like:

				# objects creation:
				ivan = Student("Ivan Ivanov", ["maths", "phisics"])
				alex = Student("Alex Petrov", ["arts", "music"])
				maria = Student("Maria Popova", ["chemistry", "biology"])
		

The Task

Define method greet() which will print greeting message from each student, as in the next code snippet:

			# use objects:
			ivan.greet()
			alex.greet()
			maria.greet()

			#OUTPUT:
			#Hello, I'm Ivan Ivanov. My favourite subjects are:
			#	maths
			#	phisics
			#Hello, I'm Alex Petrov. My favourite subjects are:
			#	arts
			#	music
			#Hello, I'm Maria Popova. My favourite subjects are:
			#	chemistry
			#	biology
		

You can align text one tab left, using the "\t" escape in the string.

Submission

Please, prefix your filenames/archive with your name initials, before sending.
For instance: iep_task1.py or iep_tasks.rar
Send files to progressbg.python.course@gmail.com

These slides are based on

customised version of

Hakimel's reveal.js

framework