Объектно-ориентированное программирование, также известное как ООП, является одной из тех важных тем, в которых любой программист должен иметь полное представление. Прежде чем углубляться в эту тему, давайте определим, что такое ООП в целом и почему это обязательный навык для любого программиста. ООП в Python — это метод структурирования программы путем объединения связанных свойств и поведения в отдельные объекты.

Почему ООП?

Объектно-ориентированное программирование дает программисту множество преимуществ; некоторые из этих преимуществ перечислены ниже:

1. Это делает ваш код многоразовым

2. Это делает ваш код чистым и придает ему четкую структуру

3. Это упрощает поддержку вашего кода

Структура ООП

1. Классы

  • Это определяемые пользователем типы данных, которые действуют как схема для объектов, методов и атрибутов или являются контейнерами объектов, методов и атрибутов.

2. Объекты

  • Это экземпляры класса, созданные со специально определенными данными.

3. Методы

  • Это функции, определенные внутри класса, описывающие поведение объектов.

4. Атрибуты

  • Они представляют состояние объектов в классе

Создание нашего первого класса

Мы можем создать класс, используя ключевое слово class, за которым следует имя класса и двоеточие в конце.

Синтаксис:

class ClassName:
    statement1
        .
        .
    statementN

Если вы выполните print(type(Car)), вы получите следующий вывод:

Объекты класса

Объект является экземпляром класса, и должно быть известно, что мы можем иметь как можно больше объектов из класса. Процесс создания объекта класса также называется созданием экземпляра объекта, вы можете создать объект только для существующего класса.

Синтаксис:

object_name = ClassName()

Пример 2:

audi = Car()

audi — это объект класса Car.

Создание класса и его объектов

# creating a class Car
class Car:
    pass
# instantiating the Car class
car1 = Car()
car2 = Car()
# creating attributes for instances car1 and car2
car1.name = "Audi A7"
car2.name = "Nissan Demio"
car1.year_made = "2000"
car2.year_made = "1994"
# printing the output
print(car1.name, "was built in", car1.year_made)
print(car2.name, "was built in", car2.year_made)

Вывод:

Методы

Это функции, определенные внутри класса, которые описывают поведение объектов, привязанных к этому классу. Методы внутри класса всегда принимают self в качестве первого параметра.

Пример 3. Создание методов внутри класса

# creating a class Car
class Car:
    # initializing method(one of the special methods)
    # called everytime an instance is created
    # takes self always
    def __init__(self, name, year_made):
        self.name = name
        self.year_made = year_made
    # normal method to return the car info   
    def carInformation(self):
        return self.name + " was built in " + self.year_made

#instantiating class objects 
car1 = Car("Audi A7", "2000")
car2 = Car("Nissan Demio", "1994")
# calling the carInformation() method
print(car1.carInformation())
print(car2.carInformation())

Вывод:

Примечание.

Любой объект имеет доступ ко всему, что содержится в классе, например, объект car1 имеет доступ к методу carInformation().

Давайте теперь поговорим о методе __init__ или dunder init, также известном как конструктор. Этот метод используется для инициализации экземпляра, и его имя фиксировано (не может быть переименовано во что-либо). Именно внутри метода __init__ мы определяем или создаем экземпляры атрибутов класса, помимо атрибутов self — это еще один параметр, который принимает метод __init__.

def __init__(self, name, year_made):
    self.name = name
    self.year_made = year_made

Другие специальные методы, используемые в классах

__str__ используется для красивого отображения данных.

Пример:

# creating a class Car
class Car:
    def __init__(self, name, year_made):
        self.name = name
        self.year_made = year_made
    #the __str__ function for display pretty output 
    def __str__(self):
        return self.name
# instantiating an object
car1 = Car("Range Rover", "2019")
# calling the whole object
print(car1)

Вывод:

__repr__используется для регистрации и отладки.

Пример:

# creating a class Car
class Car:
    def __init__(self, name, year_made):
        self.name = name
        self.year_made = year_made
    #the __repr__ function for display pretty output 
    def __repr__(self):
        return self.name
# instantiating an object
car1 = Car("Range Rover", "2019")
# calling the whole object
print(car1)

Вывод:

Примечание.

__str__ и __repr__ не сильно отличаются по своей природе.

Переменные класса и экземпляра

Когда вы создаете свои классы, вы можете захотеть работать с переменными класса или переменными экземпляра или работать с обоими, так чем же тогда они отличаются? В приведенной ниже таблице все объясняется:

Пример 4. Переменные класса

# creating a class Car
class Car:
    # creating the class variable after class definition
    maximum_speed = "210 km/hr"
    # initializing method(one of the special methods)
    # called everytime an instance is created
    # takes self always
    def __init__(self, name, year_made):
        self.name = name
        self.year_made = year_made
    # normal method to return the car info   
    def carInformation(self):
        return self.name + " was built in " + self.year_made

#instantiating class objects 
car1 = Car("Audi A7", "2000")
# calling the carInformation() method
print(car1.carInformation())
# accessing class and instance variable using the object
print(car1.name, "has maximum speed of", car1.maximum_speed)

Вывод:

Пример 5: переменные экземпляра

# creating the class
class Car:
    # initializing method
    def __init__(self, name, year_made):
        # 2 instance variables name and year_made
        self.name = name
        self.year_made = year_made
#instantiating the class object   
car1 = Car("Audi A7", "2000")
# car1.name and car1.year_made accesses instance variables
print("Car name:", car1.name)
print("Year made:", car1.year_made)

Вывод:

Наследование

Наследование в объектно-ориентированном программировании — это концепция, в которой новый класс создается с использованием уже существующего класса. Поскольку вы наследуете от класса, это означает, что вы получаете методы и атрибуты этого класса, короче говоря, вы получаете все, что содержится в этом классе.

Почему наследование?

Наследование позволяет повторно использовать код, это означает, что нам не нужно писать один и тот же код снова и снова, и мы можем легко повторно использовать метод с теми же или измененными данными.

Типы наследования

1. Одиночное наследование

  • Дочерний класс наследуется только от одного родительского класса

Синтаксис:

class ParentClass:
    statement(s)
     
class ChildClass(ParentClass):
    statement(s)

2. Множественное наследование

  • Дочерний класс наследуется от нескольких родительских классов

Синтаксис:

class ParentClass:
    statement(s)
    
class AnotherParentClass:
    statement(s)
     
class ChildClass(ParentClass, AnotherParentClass):
    statement(s)

Пример 1. Использование наследования

class Car:
    # creating class variables
    maximum_speed = "210 km/hr"
    number_of_tyres = 4
    # the dunder init method/initializer
    def __init__(self, name, year_made):
        self.name = name
        self.year_made = year_made
        
# class Minibus inheriting from class Car
class Minibus(Car):
    # __init__ method of class Minibus
    def __init__(self, name, year_made, price):
        self.name = name
        self.year_made = year_made
        self.price = price
    # method to return price
    def carPrice(self):
        return self.price
# instantiating the object for class Minibus
minbus1 = Minibus("Vanetti", "2013", "MWK4500000")
# printing data
print("Name:", minbus1.name)#name for Minibus class
print("Year made:", minbus1.year_made)#year for Minibus class
print("Maximum speeed:", minbus1.maximum_speed)#max_speed inherited from Car
print("Number of tyres:", minbus1.number_of_tyres)#number_of_tyres inherited from Car
print("Price:", minbus1.carPrice())#calling the carPrice() method in Minibus

Вывод:

Инкапсуляция

Когда вы пишете свои программы с помощью объектно-ориентированного программирования, возникают ситуации, когда вы хотите, чтобы данные вашего класса никоим образом не изменялись, чтобы решить эту инкапсуляцию. Инкапсуляция определяется как способ ограничения доступа к методам и переменным, доступным в классе. Используя инкапсуляцию, вы можете сделать атрибуты класса закрытыми или защищенными.

1. Частный

  • Эти атрибуты невидимы и недоступны снаружи самого класса.

Синтаксис:

def __init__(self, name, year_made):
    self.__year_made = year_made#this attribute is private

Мы используем двойное подчеркивание (__) для обозначения закрытого атрибута.

Пример 2. Создание закрытого атрибута

# creating our class Car
class Car:
    # initializing attribute name via __init__
    def __init__(self, name):
        self.__name = name#this is a private attribute
        
#instantiating an object   
car1 = Car("BMW")
# printing the private attribute
print(car1.name)

Вывод:

Мы получаем ошибку, потому что закрытый атрибут невидим и недоступен вне класса

2. Защищено

  • Доступ к этому атрибуту возможен только через подкласс, но возможен доступ к ним вне класса.

Синтаксис:

def __init__(self, name, year_made):
    self._name = name#this attribute is protected

Мы используем одиночное подчеркивание (_) для обозначения защищенного атрибута.

Пример 3. Создание защищенного атрибута

# creating our class Car
class Car:
    # initializing attribute name via __init__
    def __init__(self, name):
        self._name = name#this is a protected attribute
        
#instantiating an object   
car1 = Car("BMW")
# printing the protected attribute
print(car1._name)

Вывод:

Чтобы получить доступ к защищенному атрибуту вне класса, мы используем одиночное подчеркивание перед именем атрибута, например. car1._name

Примечание.

Атрибуты без каких-либо символов подчеркивания называются общедоступными, это означает, что они видны и доступны даже извне самого класса с использованием объекта.

Полиморфизм

Поли означает много, а морфизм означает формы, поэтому полиморфизм означает наличие многих форм. В Python полиморфизм означает, что одно и то же имя функции используется по-разному.

Пример 4: Использование полиморфизма

# creating class Square
class Square:
    # initializer
    def __init__(self, side):
        self.side = side 
    # common method
    def calculate_area(self):
        return self.side * self.side
# creating class Triangle
class Triangle:
    # initializer
    def __init__(self, base, height):
        self.base = base
        self.height = height
    # common method
    def calculate_area(self):
        return 0.5 * self.base * self.height
# instantiating class objects
sq = Square(6)
tri = Triangle(2, 7)
# printing results
print("Area of square: ", sq.calculate_area())
print("Area of triangle: ", tri.calculate_area())

Вывод:

В наших двух классах мы создали общий метод calculate_area(), который используется по-разному, то есть вычисляет площадь квадрата и треугольника.

Заключение

Таким образом, освоение объектно-ориентированных концепций является обязательным навыком для любого разработчика. Следование объектно-ориентированной парадигме при написании кода делает его более чистым, удобным в сопровождении и повторно используемым.