Object-oriented programming (OOP) involves creating objects that can be referenced/represented in your application using a class or object.
Classes: A blueprint for objects. Can contain methods (functions) and attributes (similar to keys in a dictionary)
Objects: An instance of a class blueprint that contains their class’s methods and properties.
OOP aims to encapsulate your code into logical hierarchical groupings using classes.
# Define a class named Car
class Car:
# Class constructor to initialize the attributes
def __init__(self, make, model, year):
# Instance attributes
self.make = make
self.model = model
self.year = year
# Instance method
def display_info(self):
print(f"{self.year} {self.make} {self.model}")
# Create an object of the Car class
my_car = Car("Toyota", "Corolla", 2020)
# Use the object to call the method
my_car.display_info() # Output: 2020 Toyota CorollaWhen a new class is created, the def __init__ (self): method should also be created to initialise the class with a ‘self’ property.
‘Self’ refers to the specific instance of the class you are working with. But you can include instance attributes within your class to define attributes that make up that class. Ensure you include a new parameter of that attribute in the init definition.
class User:
def __init__(self, name):
self.name = nameAnd then you can define an attribute within the class as a parameter.
class User:
def __init__(self, name):
self.name = name
user1 = User('John')
print(user1.name) # JohnIf there are other attributes required then provide data for each attribute, otherwise, you will get an error.
class User:
def __init__(self, name, age, city):
self.name = name
self.age = age
self.city = city
user1 = User('John', 18, 'Paris')
print(user1.name) # John
print(user1.age) # 18
print(user1.city) # ParisHow attributes and methods are defined in classes matters. If the attribute or method has a single underscore before it then this is a convention that means this is a private attribute or method. But it is not strictly private/secret as it can still be printed/displayed/operated. However, if there are two underscores before the property/method then this means that this is specific to a particular class based on inheritance. You cannot just print person.__msg as the name changes to being variable.class__nameOfProperty (person.Person__msg).
class Person:
def __init__ (self):
self.name = "Tony"
self._secret = "hi!"
self.__msg = "I like turtles"
p = Person()
print(p.name) # Tony
print(p._secret) # hi!
print(p.__msg) # AttributeError: 'Person' object has no attribute '__msg'
print(p._Person__msg) # I like turtlesClasses can also contain instance methods that perform a function. They are called as any function would be called using the name of the function with parentheses afterwards.
class User:
def __init__(self, firstname, surname, age, city):
self.firstname = firstname
self.surname = surname
self.age = age
self.city = city
def full_name (self):
return self.firstname + ' ' + self.surname
user1 = User('John', 'Doe', 18, 'Paris')
print(user1.full_name())You can also define attributes directly on a class once and they are shared by all instances of a class and the class itself.
Class attributes do not use ‘self’ and can be called/operated/updated but are linked specifically to the class itself. The below example updates the number of active users every time a user is created/initiated.
class User:
# Class attribute
active_users = 0
def __init__(self, firstname, surname, age, city):
# Instance attributes
self.firstname = firstname
self.surname = surname
self.age = age
self.city = city
# Update the class attribute
User.active_users += 1
#Instance methods
def full_name (self):
return self.firstname + ' ' + self.surname
def initials (self):
return f"{self.firstname[0] + '.' + self.surname[0]+'.'}"
user1 = User('John', 'Doe', 18, 'Paris')
print(user1.full_name()) # John Doe
print(user1.initials()) # J.D.
print (User.active_users) # 1
user2 = User('Lucy', 'Bridge', 21, 'Paris')
print (User.active_users) # 2class Pet:
allowed = ['cat', 'dog', 'fish', 'bird']
def __init__(self, name, species):
if species not in Pet.allowed:
raise ValueError(f"You can't have a {species} pet!")
self.name = name
self.species = species
def set_species(self, species):
if species not in Pet.allowed:
raise ValueError(f"You can't have a {species} pet!")
self.species = species
cat = Pet('Blue', 'cat')
dog = Pet('Wyatt', 'dog')
bird = Pet('Tweety', 'bird')
rat = Pet('Jerry', 'rat') # ValueError: You can't have a rat pet!Class methods are methods with the @classmethod decorator that are concerned with the class itself but not with instances.
class User:
# Class attribute
active_users = 0
def __init__(self, firstname, surname, age, city):
# Instance attributes
self.firstname = firstname
self.surname = surname
self.age = age
self.city = city
# Update the class attribute
User.active_users += 1
#Instance methods
def full_name (self):
return self.firstname + ' ' + self.surname
def initials (self):
return f"{self.firstname[0] + '.' + self.surname[0]+'.'}"
# Class method
@classmethod
def display_active_users(cls):
return f"There are currently {cls.active_users} active users"
user1 = User('John', 'Doe', 18, 'Paris')
print(user1.full_name()) # John Doe
print(user1.initials()) # J.D.
print (User.active_users) # 1
user2 = User('Lucy', 'Bridge', 21, 'Paris')
print (User.active_users) # 2
print(User.display_active_users()) # There are currently 2 active usersThe __repr__ method is a string representation of the class. This type of string representation is more for internal use (for example, for developers).
class User:
# Class attribute
active_users = 0
def __init__(self, firstname, surname, age, city):
# Instance attributes
self.firstname = firstname
self.surname = surname
self.age = age
self.city = city
# Update the class attribute
User.active_users += 1
def __repr__ (self):
return f"<{self.firstname}, {self.age}>"
def __str__ (self):
return f"{self.firstname} is {self.age}"
user1 = User('Lucy', 'Bridge', 21, 'Paris')
print (user1) # Lucy is 21A key feature of OOP is the ability to define a class which inherits from another class (a ‘base’ or ‘parent’ class). The below code block has a base class of Animal and derived classes for two animals (Dog and Cat) that uses the base class’s attributes but the attribute has been made specific to them.
# Base class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
# Derived class
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
# Derived class
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
# Creating instances of the derived classes
dog = Dog("Buddy")
cat = Cat("Whiskers")
# Calling the speak method on the instances
print(dog.speak()) # Output: Buddy says Woof!
print(cat.speak()) # Output: Whiskers says Meow!The @property decorator in Python is used to define methods in a class that behave like attributes. It allows you to define getter, setter, and deleter methods for a class attribute.
The Getter Method (@property decorator) is used to define a method that gets the value of an attribute. This method can be accessed like an attribute, without parentheses.
The Setter Method (@property_name.setter decorator) is used to define a method that sets the value of an attribute. This allows you to include validation or other logic when setting the attribute's value.
The Deleter Method (@property_name.deleter decorator) is used to define a method that deletes an attribute. This is less commonly used but provides a way to manage attribute deletion.
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value:
raise ValueError("Name cannot be empty")
self._name = value
@name.deleter
def name(self):
del self._name
# Usage
p = Person("Alice")
print(p.name) # Accessing the getter: Output: Alice
p.name = "Bob" # Using the setter
print(p.name) # Output: Bob
del p.name # Using the deletersuper() in a class refers to the base/parent class. The below code has a base class of Animal and a derived class of Cat:
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def __repr__(self):
return f"{self.name} is a {self.breed} and loves to play with {self.toy}."
class Cat(Animal):
def __init__(self, name, species, breed, toy):
self.name = name
self.species = species
self.breed = breed
self.toy = toy
blue = Cat('Blue', 'Cat', 'Scottish Fold', 'Ball')
print(blue) # Blue is a Scottish Fold and loves to play with Ball.However, when creating the Cat class, all of the attributes have to be passed from the Animal class and defined in the Cat class. super() can be used to reference the Animal class and pass attributes down that are not specific to the Cat class.
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def __repr__(self):
return f"{self.name} is a {self.species}"
class Cat(Animal):
def __init__(self, name, species, breed, toy):
super().__init__(name, species)
self.breed = breed
self.toy = toy
blue = Cat('Blue', 'Cat', 'Scottish Fold', 'Ball')
print(blue) # Blue is a Scottish Fold and loves to play with Ball.Methods can also be used when super() is inheriting from the base class.
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def __repr__(self):
return f"{self.name} is a {self.species}"
class Cat(Animal):
def __init__(self, name, breed, toy):
super().__init__(name, species="Cat")
self.breed = breed
self.toy = toy
def play(self):
print(f"{self.name} plays with {self.toy}")
blue = Cat('Blue', 'Scottish Fold', 'Ball')
print(blue) # Blue is a Scottish Fold and loves to play with Ball.
blue.play() # Blue plays with Ball.