Объектно-ориентированное программирование. Общее представление

Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.

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

Python соответствует принципам объектно-ориентированного программирования. В python всё является объектами - и строки, и списки, и словари, и всё остальное.

Но возможности ООП в python этим не ограничены. Программист может написать свой тип данных (класс), определить в нём свои методы.

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

Приступим теперь собственно к написанию своих классов на python. Попробуем определить собственный класс:

# Пример самого простейшего классаclass A: pass

Теперь мы можем создать несколько экземпляров этого класса:

>>> a = A()>>> b = A()>>> a.arg = 1 # у экземпляра a появился атрибут arg, равный 1>>> b.arg = 2 # а у экземпляра b - атрибут arg, равный 2>>> print(a.arg)1

Классу возможно задать собственные методы:

class A: def g(self): # self - обязательный аргумент, содержащий в себе экземпляр класса, передающийся при вызове метода, # поэтому этот аргумент должен присутствовать во всех методах класса. return 'hello world' >>> a = A()>>> a.g()'hello world'

И еще один пример:

class B: arg = 'Python' # Все экземпляры этого класса будут иметь атрибут arg, равный "Python" # Но впоследствии мы его можем изменить def g(self): return self.arg >>> b = B()>>> b.g()'Python'>>> B.g(b)'Python' >>> b.arg = 'spam'>>> b.g()'spam'

Инкапсуляция, наследование, полиморфизм

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

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

Инкапсуляция — ограничение доступа к составляющим объект компонентам (методам и переменным). Инкапсуляция делает некоторые из компонент доступными только внутри класса.

Инкапсуляция в Python работает лишь на уровне соглашения между программистами о том, какие атрибуты являются общедоступными, а какие — внутренними.

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

class A: def _private(self): print("Это приватный метод!") >>> a = A()>>> a._private()Это приватный метод!

Двойное подчеркивание в начале имени атрибута даёт большую защиту: атрибут становится недоступным по этому имени.

class B: def __private(self): print("Это приватный метод!") >>> b = B()>>> b.__private()Traceback (most recent call last): File "", line 1, in b.__private()AttributeError: 'B' object has no attribute '__private'

Однако полностью это не защищает, так как атрибут всё равно остаётся доступным под именем _ИмяКласса__ИмяАтрибута:

>>> b._B__private()Это приватный метод!

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

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

class Mydict(dict): def get(self, key, default = 0): return dict.get(self, key, default) a = dict(a=1, b=2)b = Mydict(a=1, b=2)

Класс Mydict ведёт себя точно так же, как и словарь, за исключением того, что метод get по умолчанию возвращает не None, а 0.

>>> b['c'] = 4>>> print(b){'a': 1, 'c': 4, 'b': 2}>>> print(a.get('v'))None>>> print(b.get('v'))0

Полиморфизм

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

>>> 1 + 12>>> "1" + "1"'11'

Перегрузка операторов

Перегрузка операторов — один из способов реализации полиморфизма, когда мы можем задать свою реализацию какого-либо метода в своём классе.

Например, у нас есть два класса:

class A: def go(self): print('Go, A!') class B(A): def go(self, name): print('Go, {}!'.format(name))

В данном примере класс B наследует класс A, но переопределяет метод go, поэтому он имеет мало общего с аналогичным методом класса A.

Однако в python имеются методы, которые, как правило, не вызываются напрямую, а вызываются встроенными функциями или операторами.

Например, метод __init__ перегружает конструктор класса. Конструктор - создание экземпляра класса.

class A: def __init__(self, name): self.name = name >>> a = A('Vasya')>>> print(a.name)Vasya

Собственно, далее пойдёт список таких "магических" методов.

__new__(cls[, ...]) — управляет созданием экземпляра. В качестве обязательного аргумента принимает класс (не путать с экземпляром). Должен возвращать экземпляр класса для его последующей его передачи методу __init__.

__init__(self[, ...]) - как уже было сказано выше, конструктор.

__del__(self) - вызывается при удалении объекта сборщиком мусора.

__repr__(self) - вызывается встроенной функцией repr; возвращает "сырые" данные, использующиеся для внутреннего представления в python.

__str__(self) - вызывается функциями str, print и format. Возвращает строковое представление объекта.

__bytes__(self) - вызывается функцией bytes при преобразовании к байтам.

__format__(self, format_spec) - используется функцией format (а также методом format у строк).

__lt__(self, other) - x < y вызывает x.__lt__(y).

__le__(self, other) - x ≤ y вызывает x.__le__(y).

__eq__(self, other) - x == y вызывает x.__eq__(y).

__ne__(self, other) - x != y вызывает x.__ne__(y)

__gt__(self, other) - x > y вызывает x.__gt__(y).

__ge__(self, other) - x ≥ y вызывает x.__ge__(y).

__hash__(self) - получение хэш-суммы объекта, например, для добавления в словарь.

__bool__(self) - вызывается при проверке истинности. Если этот метод не определён, вызывается метод __len__ (объекты, имеющие ненулевую длину, считаются истинными).

__getattr__(self, name) - вызывается, когда атрибут экземпляра класса не найден в обычных местах (например, у экземпляра нет метода с таким названием).

__setattr__(self, name, value) - назначение атрибута.

__delattr__(self, name) - удаление атрибута (del obj.name).

__call__(self[, args...]) - вызов экземпляра класса как функции.

__len__(self) - длина объекта.

__getitem__(self, key) - доступ по индексу (или ключу).

__setitem__(self, key, value) - назначение элемента по индексу.

__delitem__(self, key) - удаление элемента по индексу.

__iter__(self) - возвращает итератор для контейнера.

__reversed__(self) - итератор из элементов, следующих в обратном порядке.

__contains__(self, item) - проверка на принадлежность элемента контейнеру (item in self).

Наши рекомендации