Object-Oriented Programming in Python Overview
Object-Oriented Programming in Python Overview
Classes
The focal point of Object Oriented Programming (OOP) are objects, which are created using classes.
The class describes what the object will be, but is separate from the object itself. In other words, a class can be
described as an object's blueprint, description, or definition.
You can use the same class as a blueprint for creating multiple different objects.
Classes are created using the keyword class and an indented block, which contains class methods (which are
functions).
Below is an example of a simple class and its objects.
class Cat:
def __init__(self, color, legs):
self.color = color
self.legs = legs
felix = Cat("ginger", 4)
rover = Cat("dog-colored", 4)
stumpy = Cat("brown", 3)
This code defines a class named Cat, which has two attributes: color and legs.
Then the class is used to create 3 separate objects of that class.
__init__
All methods must have self as their first parameter, although it isn't explicitly passed, Python adds the self argument
to the list for you; you do not need to include it when you call the methods. Within a method definition, self refers
to the instance calling the method.
Instances of a class have attributes, which are pieces of data associated with them.
In this example, Cat instances have attributes color and legs. These can be accessed by putting a dot, and the
attribute name after an instance.
In an __init__ method, self.attribute can therefore be used to set the initial value of an instance's attributes.
Example:
class Cat:
def __init__(self, color, legs):
self.color = color
self.legs = legs
felix = Cat("ginger", 4)
print(felix.color)
ginger
In the example above, the __init__ method takes two arguments and assigns them to the object's attributes.
The __init__ method is called the class constructor.
Methods
Example:
class Dog:
def __init__(self, name, color):
self.name = name
self.color = color
def bark(self):
print("Woof!")
Sample Input
Tony
12
Sample Output
Tony (Level 12)
Use the dot syntax to call the intro() method for the declared object.
class Player:
def __init__(self, name, level):
self.name = name
self.level = level
def intro(self):
print(self.name + " (Level " + self.level + ")")
name=input()
level=input()
p=Player(name,level)
p.intro()
Input
abc X
Output
abc (Level X)
Inheritance
Example:
class Animal:
def __init__(self, name, color):
self.name = name
self.color = color
class Cat(Animal):
def purr(self):
print("Purr...")
class Dog(Animal):
def bark(self):
print("Woof!")
def bark(self):
print("Grr...")
class Dog(Wolf):
def bark(self):
print("Woof")
Woof
In the example above, Wolf is the superclass, Dog is the subclass.
The function super is a useful inheritance-related function that refers to the parent class. It can be used to find the
method with a certain name in an object's superclass.
Example:
class A:
def spam(self):
print(1)
class B(A):
def spam(self):
print(2)
super().spam()
B().spam()
2
1
super().spam() calls the spam method of the superclass.
Problem
You are making a drawing application, which has a Shape base class.
The given code defines a Rectangle class, creates a Rectangle object and calls its area() and perimeter() methods.
class Shape:
def __init__(self, w, h):
self.width = w
self.height = h
def area(self):
print(self.width*self.height)
class Rectangle(Shape):
def perimeter(self):
print(2*(self.width+self.height))
w = int(input())
h = int(input())
r = Rectangle(w, h)
r.area()
r.perimeter()
Input
12 42
Output
504 108
Magic Methods
Magic methods are special methods which have double underscores at the beginning and end of their names.
They are also known as dunders.
So far, the only one we have encountered is __init__, but there are several others.
They are used to create functionality that can't be represented as a normal method.
first = Vector2D(5, 7)
second = Vector2D(3, 9)
result = first + second
print(result.x)
print(result.y)
8
16
The __add__ method allows for the definition of a custom behavior for the + operator in our class.
As you can see, it adds the corresponding attributes of the objects and returns a new object, containing the result.
Once it's defined, we can add two objects of the class together.
spam = SpecialString("spam")
hello = SpecialString("Hello world!")
print(spam / hello)
spam
============
Hello world!
In the example above, we defined the division operation for our class SpecialString.
Example:
class SpecialString:
def __init__(self, cont):
self.cont = cont
spam = SpecialString("spam")
eggs = SpecialString("eggs")
spam > eggs
>spam>eggs
e>spam>ggs
eg>spam>gs
egg>spam>s
eggs>spam>
As you can see, you can dThere are several magic methods for making classes act like containers.
__len__ for len()
__getitem__ for indexing
__setitem__ for assigning to indexed values
__delitem__ for deleting indexed values
__iter__ for iteration over objects (e.g., in for loops)
__contains__ for in
There are many other magic methods that we won't cover here, such as __call__ for calling objects as functions,
and __int__, __str__, and the like, for converting objects to built-in types.
Example:
import random
class VagueList:
def __init__(self, cont):
self.cont = cont
def __len__(self):
return random.randint(0, len(self.cont)*2)
The addition should return a new object with the sum of the widths and heights of the operands, while the
comparison should return the result of comparing the areas of the objects.
The given code creates two Shape objects from user input, outputs the area() of their addition and compares them.
class Shape:
def __init__(self, w, h):
self.width = w
self.height = h
def area(self):
return self.width * self.height
w1 = int(input())
h1 = int(input())
w2 = int(input())
h2 = int(input())
s1 = Shape(w1, h1)
s2 = Shape(w2, h2)
result = s1 + s2
print(result.area())
print(s1 > s2)
Input
2513
Output
24
True
Data Hiding
A key part of object-oriented programming is encapsulation, which involves packaging of related variables and
functions into a single easy-to-use object -- an instance of a class.
A related concept is data hiding, which states that implementation details of a class should be hidden, and a clean
standard interface be presented for those who want to use the class.
In other programming languages, this is usually done with private methods and attributes, which block external
access to certain methods and attributes in a class.
The Python philosophy is slightly different. It is often stated as "we are all consenting adults here", meaning that
you shouldn't put arbitrary restrictions on accessing parts of a class. Hence there are no ways of enforcing that a
method or attribute be strictly private.
However, there are ways to discourage people from accessing parts of a class, such as by denoting that it is an
implementation detail, and should be used at their own risk.
class Queue:
def __init__(self, contents):
self._hiddenlist = list(contents)
def pop(self):
return self._hiddenlist.pop(-1)
def __repr__(self):
return "Queue({})".format(self._hiddenlist)
Queue([1, 2, 3])
Queue([0, 1, 2, 3])
Queue([0, 1, 2])
[0, 1, 2]
In the code above, the attribute _hiddenlist is marked as private, but it can still be accessed in the outside code.
The __repr__ magic method is used for string representation of the instance.
Strongly private methods and attributes have a double underscore at the beginning of their names. This causes their
names to be mangled, which means that they can't be accessed from outside the class.
The purpose of this isn't to ensure that they are kept private, but to avoid bugs if there are subclasses that have
methods or attributes with the same names.
Name mangled methods can still be accessed externally, but by a different name. The method __privatemethod of
class Spam could be accessed externally with _Spam__privatemethod.
class Spam:
__egg = 7
def print_egg(self):
print(self.__egg)
s = Spam()
s.print_egg()
print(s._Spam__egg)
print(s.__egg)
7
7
Basically, Python protects those members by internally changing the name to include the class name.
Problem
We are working on a game. Our Player class has name and private _lives attributes.
The hit() method should decrease the lives of the player by 1. In case the lives equal to 0, it should output "Game
Over".
Complete the hit() method to make the program work as expected.
The code creates a Player object and calls its hit() method multiple times.
class Player:
def __init__(self, name, lives):
self.name = name
self._lives = lives
def hit(self):
self._lives-=1
if self._lives==0:
print("Game Over")
p = Player("Cyberpunk77", 3)
p.hit()
p.hit()
p.hit()
Class Methods
Methods of objects we've looked at so far are called by an instance of a class, which is then passed to
the self parameter of the method.
Class methods are different -- they are called by a class, which is passed to the cls parameter of the method.
A common use of these are factory methods, which instantiate an instance of a class, using different parameters
than those usually passed to the class constructor.
Class methods are marked with a classmethod decorator.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def calculate_area(self):
return self.width * self.height
@classmethod
def new_square(cls, side_length):
return cls(side_length, side_length)
square = Rectangle.new_square(5)
print(square.calculate_area())
25
new_square is a class method and is called on the class, rather than on an instance of the class. It returns a new
object of the class cls.
Technically, the parameters self and cls are just conventions; they could be changed to anything else. However, they
are universally followed, so it is wise to stick to using them.
Static Methods
Static methods are similar to class methods, except they don't receive any additional arguments; they are identical
to normal functions that belong to a class.
They are marked with the staticmethod decorator.
Example:
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@staticmethod
def validate_topping(topping):
if topping == "pineapple":
raise ValueError("No pineapples!")
else:
return True
Problem
The given code takes 2 numbers as input and calls the static area() method of the Shape class, to output the area of
the shape, which is equal to the height multiplied by the width.
To make the code work, you need to define the Shape class, and the static area() method, which should return the
multiplication of its two arguments.
Use the @staticmethod decorator to define a static method.
class Shape:
@staticmethod
def area(height, width):
return height * width
w = int(input())
h = int(input())
print(Shape.area(w, h))
Input
4
5
Output
20
Properties
@property
def pineapple_allowed(self):
return self._pineapple_allowed
@pineapple_allowed.setter
def pineapple_allowed(self, value):
self._pineapple_allowed = value
@property
def pineapple_allowed(self):
return self._pineapple_allowed
@pineapple_allowed.setter
def pineapple_allowed(self, value):
if value:
password = input("Enter the password: ")
if password == "Sw0rdf1sh!":
self._pineapple_allowed = value
else:
raise ValueError("Alert! Intruder!")
We are improving our game and need to add an isAlive property, which returns True if the lives count is greater than
0.
Complete the code by adding the isAlive property.
The code uses a while loop to hit the Player, until its lives count becomes 0, using the isAlive property to make the
condition.
class Player:
def __init__(self, name, lives):
self.name = name
self._lives = lives
def hit(self):
self._lives -= 1
@property
def isAlive(self):
if self._lives > 0:
return True
p = Player("Cyberpunk77", int(input()))
i=1
while True:
p.hit()
print("Hit # " + str(i))
i += 1
if not p.isAlive:
print("Game Over")
break
Input
7
Output
Hit # 1
Hit # 2
Hit # 3
Hit # 4
Hit # 5
Hit # 6
Hit # 7
Game Over
Problem
Sample Input
laser
laser
gun
exit
Sample Output
Alien has 4 lives
Alien has 3 lives
Monster has 2 lives
class Enemy:
name = ""
lives = 0
def __init__(self, name, lives):
self.name = name
self.lives = lives
def hit(self):
self.lives -= 1
if self.lives <= 0:
print(self.name + ' killed')
else:
print(self.name + ' has '+ str(self.lives) + ' lives')
m = Enemy('Monster',3)
a = Enemy('Alien',5)
while True:
x = input()
if x == 'exit':
break
elif x=='laser':
a.hit()
else:
m.hit()
Input
gun
gun
gun
exit
Output
Monster has 2 lives
Monster has 1 lives
Monster killed