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

Процессно-ориентированное программирование рассматривает компьютерную программу как серию наборов команд, то есть последовательное выполнение набора функций. В целях упрощения программирования процессно-ориентированные функции по-прежнему делятся на подфункции, то есть большие функции разбиваются на маленькие, чтобы уменьшить сложность системы.

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

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

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

Предположим, мы хотим обработать список оценок ученика. Чтобы представить оценку ученика, процессно-ориентированную программу можно представить в виде диктата:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

Обработку оценок учащихся можно реализовать с помощью таких функций, как печать оценок учащихся:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

Если мы примем идеи объектно-ориентированного программирования, нашим главным приоритетом будет не думать о процессе выполнения программы, а рассматривать тип данных Student как объект, имеющий два атрибута: name и score. Если вы хотите распечатать оценку учащегося, вы должны сначала создать объект, соответствующий учащемуся, а затем отправить объекту сообщение print_score, чтобы объект мог распечатать свои собственные данные.

class Student(object):

def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

Отправка сообщения объекту фактически вызывает связанную с ним функцию, которую мы называем методом объекта. Объектно-ориентированная программа пишется так:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

Идея объектно-ориентированного проектирования исходит из природы, поскольку в природе понятия класса и экземпляра очень естественны. Класс — это абстрактное понятие. Например, класс — Студент, который мы определяем, относится к концепции студентов, а Экземпляр — это конкретный Студент. Например, Барт Симпсон и Лиза Симпсон — два конкретных студента.

Таким образом, идея объектно-ориентированного проектирования заключается в абстрагировании класса и создании экземпляра на основе класса.

Уровень абстракции объектно-ориентированных выше, чем у функций, поскольку класс содержит как данные, так и методы для работы с данными.

Класс и экземпляр

Наиболее важными понятиями объектно-ориентированного подхода являются класс и экземпляр. Необходимо помнить, что класс — это абстрактный шаблон, такой как класс Student, а экземпляр — это конкретный «объект», созданный на основе класса. Каждый объект имеет один и тот же метод, но отдельные данные могут отличаться.

Продолжая рассматривать класс Student в качестве примера, определение класса в Python осуществляется с помощью ключевого слова class:

class Student(object):
    pass

Сразу после class указывается имя класса, то есть Student. Имя класса обычно представляет собой слово, начинающееся с заглавной буквы, за которым следует object, указывающее, от какого класса унаследован класс. О концепции наследования мы поговорим позже. Если подходящего класса наследования нет, просто используйте класс object, который в конечном итоге унаследуют все классы.

После определения класса Student вы можете создать экземпляр Student на основе класса Student. Создание экземпляра достигается через имя класса + ():

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

Как видите, переменная bart указывает на экземпляр Student, а следующая за ней 0x10a67a590 — это адрес памяти. Адрес каждого объекта различен, а Student сам по себе является классом.

Вы можете свободно привязывать атрибуты к переменной экземпляра. Например, привяжите атрибут name к экземпляру bart:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

Поскольку классы могут функционировать как шаблоны, при создании экземпляра мы можем принудительно заполнить некоторые атрибуты, которые, по нашему мнению, должны быть связаны. Определив специальный метод __init__, при создании экземпляра к нему привязываются атрибуты name, score и другие:

class Student(object):

def __init__(self, name, score):
        self.name = name
        self.score = score
★ There are two underscores before and after the special method "__init__"! ! !

Обратите внимание, что первым параметром метода __init__ всегда является self, который представляет сам созданный экземпляр. Поэтому внутри метода __init__ можно привязывать к self различные атрибуты, поскольку self указывает на сам созданный экземпляр.

При использовании метода __init__ при создании экземпляра вы не можете передавать пустые параметры. Вы должны передать параметры, соответствующие методу __init__, но self передавать не обязательно. Интерпретатор Python сам передаст переменные экземпляра:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

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

Инкапсуляция данных

Важной особенностью объектно-ориентированного программирования является инкапсуляция данных. В приведенном выше классе Student каждый экземпляр имеет свои собственные данные name и score. Мы можем получить доступ к этим данным через такие функции, как печать оценки учащегося:

>>> def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

Поскольку экземпляр Student сам владеет этими данными, для доступа к этим данным нет необходимости обращаться к ним из внешних функций. Вы можете напрямую определить функцию доступа к данным внутри класса Student, инкапсулируя таким образом «данные». Эти функции, инкапсулирующие данные, связаны с самим классом Student, который мы называем методами класса:

class Student(object):

def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

Определение метода происходит так же, как и обычная функция, за исключением того, что первый параметр — self. Чтобы вызвать метод, вам нужно всего лишь вызвать его непосредственно в переменной экземпляра. За исключением self, его передавать не нужно. Остальные параметры передаются обычно:

>>> bart.print_score()
Bart Simpson: 59

Таким образом, когда мы смотрим на класс Student снаружи, нам нужно только знать, что для создания экземпляра необходимо указать name и score, а способ печати определяется внутри класса Student. Эти данные и логика «инкапсулированы». Позвонить легко, но подробности внутренней реализации знать не обязательно.

Еще одним преимуществом инкапсуляции является то, что к классу Student можно добавлять новые методы, например get_grade:

class Student(object):
    ...

def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

Тот же метод get_grade можно вызвать непосредственно для переменной экземпляра, не зная деталей внутренней реализации:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

Входные параметры:

lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())

Текущий результат:

Lisa A
Bart C