Объектно-ориентированное программирование, или сокращенно ООП, — это философия программирования. ООП рассматривает объекты как базовую единицу программы. Объект содержит данные и функции для рабочих данных.
Процессно-ориентированное программирование рассматривает компьютерную программу как серию наборов команд, то есть последовательное выполнение набора функций. В целях упрощения программирования процессно-ориентированные функции по-прежнему делятся на подфункции, то есть большие функции разбиваются на маленькие, чтобы уменьшить сложность системы.
Объектно-ориентированное программирование рассматривает компьютерную программу как набор объектов, и каждый объект может получать сообщения от других объектов и обрабатывать эти сообщения. Выполнение компьютерной программы представляет собой серию сообщений, передаваемых между объектами.
В 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