Объектно-ориентированное программирование. Специальные методы.
Декоратор @staticmethod определяет обычную функцию (статический метод) в пространстве имён класса. У него нет обязательного параметра-ссылки self. Может быть полезно для вспомогательных функций, чтобы не мусорить пространство имён модуля. Доступ к таким методам можно получить как из экземпляра класса, так и из самого класса:
class SomeClass(object): @staticmethod def hello(): print("Hello, world") SomeClass.hello() # Hello, world obj = SomeClass() obj.hello() # Hello, world
Hello, world Hello, world
Декоратор @classmethod создаёт метод класса и требует обязательную ссылку на класс (cls). Поэтому объект класса явно передаётся через первый параметр как это с параметром self происходит для обычных методов. Также как и для self, переданный cls может отличаться от класса, в котором определён класс-метод (может быть потомок). Часто используется для создания альтернативных конструкторов.
class SomeClass(object): @classmethod def hello(cls): print('Hello, класс <>'.format(cls.__name__)) SomeClass.hello() # Hello, класс SomeClass
Hello, класс SomeClass
Давайте взглянем на пример кода, в котором одновременно показаны она декоратора, это может помочь понять основные принципы:
class Person: def __init__(self, name, age): self.name = name self.age = age # classmethod чтобы создать объект по году рождения, # "альтернативный" конструктор @classmethod def fromBirthYear(cls, name, year): return cls(name, 2019 - year) # статический метод,чтобы проверить совершеннолетие @staticmethod def isAdult(age): return age > 18 person1 = Person('Петя', 21) person2 = Person.fromBirthYear('Петя', 1996) print(person1.age) print(person2.age) # print the result print(Person.isAdult(22))
21 23 True
Важно понимать, что ни classmethod ни staticmethod НЕ являются функциями от конкретного объекта класса и соответственно не принимают self. Подчеркнем еще раз их различия: — classmethod принимает cls как первый параметр, тогда как staticmethod в специальных аргументах не нуждается — classmethod может получать доступ или менять состояние класса, в то время как staticmethod нет — staticmethod в целом вообще ничего не знают про класс. Это просто функция над аргументами, объявленная внутри класса.
Специальные методы (магические) вида _ _
В Python существует огромное количество специальных методов, расширяющих возможности пользовательских классов. Например, можно определить вид объекта на печати, его «официальное» строковое представление или поведение при сравнениях.
Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод add. Таким образом, их нельзя переопределить.
Давайте для примера переопределим стандартную операцию сложения. Рассмотрим класс Vector, используемый для представления радиус-векторов на координатной плоскости, и определим в нем поля-координаты: x и y. Также очень хотелось бы определить для векторов операцию +, чтобы их можно было складывать столь же удобно, как и числа или строки.
Для этого необходимо перегрузить операцию +: определить функцию, которая будет использоваться, если операция + будет вызвана для объекта класса Vector. Для этого нужно определить метод add класса Vector, у которого два параметра: неявная ссылка self на экземпляр класса, для которого она будет вызвана (это левый операнд операции +) и явная ссылка other на правый операнд:
class Vector(): def __init__(self, x = 0, y = 0): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) A = Vector(1, 2) B = Vector(3, 4) C = A + B print(C.x, C.y)
Теперь при вызове оператора A + B Питон вызовет метод A.add(B), то есть вызовет указанный метод, где self = A, other = B.
class Vector: def __lt__(self, other): return self.x other.x or self.x == other.x and self.y other.y
В этом примере оператор вернет True, если у левого операнда поле x меньше, чем у правого операнда, а также если поля x у них равны, а поле y меньше у левого операнда.
Список основных перегружаемых операторов
| Метод | Использование |
|---|---|
| Операторы сравнения | |
| __lt__(self, other) | x < y |
| __le__(self, other) | x |
| __eq__(self, other) | x == y |
| __ne__(self, other) | x != y |
| __gt__(self, other) | x > y |
| __ge__(self, other) | x >= y |
| Арифметические операторы | |
| Сложение | |
| __add__(self, other) | x + y |
| __radd__(self, other) | y + x |
| __iadd__(self, other) | x += y |
| Вычитание | |
| __sub__(self, other) | x — y |
| __rsub__(self, other) | y — x |
| __isub__(self, other) | x -= y |
| Умножение | |
| __mul__(self, other) | x * y |
| __rmul__(self, other) | y * x |
| __imul__(self, other) | x *= y |
| Математическое умножение (например векторное) | |
| __matmul__(self, other) | x @ y |
| __rmatmul__(self, other) | y @ x |
| __imatmul__(self, other) | x @= y |
| Деление | |
| __truediv__(self, other) | x / y |
| __rtruediv__(self, other) | y / x |
| __itruediv__(self, other) | x /= y |
| Целочисленное деление | |
| __floordiv__(self, other) | x // y |
| __rfloordiv__(self, other) | y // x |
| __ifloordiv__(self, other) | x //= y |
| __divmod__(self, other) | divmod(x, y) |
| Остаток | |
| __mod__(self, other) | x % y |
| __rmod__(self, other) | y % x |
| __imod__(self, other) | x %= y |
| Возведение в степень | |
| __pow__(self, other) | x ** y |
| __rpow__(self, other) | y ** x |
| __ipow__(self, other) | x **= y |
| Отрицание, модуль | |
| __pos__(self) | +x |
| __neg__(self) | -x |
| __abs__(self) | abs(x) |
| __len__(self) | len(x) |
| Преобразование к стандартным типам | |
| __int__(self) | int(x) |
| __float__(self) | float(x) |
| __complex__(self) | complex(x) |
| __str__(self) | str(x) |
| __round__(self, digits = 0) | round(x, digits) |
| Блок with | |
| __enter__(self) | |
| __exit__(self) | |
Задачи:
Задача 1:
Реализуйте свой класс Complex для комплексных чисел, аналогично встроенной реализации complex:
- Добавьте инициализатор класса
- Реализуйте основные математические операции
- Реализуйте операцию модуля (abs, вызываемую как |c|)
- Оба класса должны давать осмысленный вывод как при print, так и просто при вызове в ячейке
Задача 2:
- Создайте класс Vector с полями x, y, z определите для него конструктор, метод __str__, необходимые арифметические операции. Реализуйте конструктор, который принимает строку в формате «x,y».
- Программа получает на вход число N, далее координаты N точек. Доопределите в классе Vector недостающие операторы, найдите и выведите координаты точки, наиболее удаленной от начала координат.
- Используя класс Vector выведите координаты центра масс данного множества точек.
- Даны два вектора. Выведите площадь параллелограмма, построенного на заданных векторах.
- Даны три вектора. Выведите объём параллелепипеда, построенного на заданных векторах.
- Среди данных точек найдите три точки, образующие треугольник с наибольшим периметром. Выведите данный периметр.
- Среди данных точек найдите три точки, образующие треугольник с наибольшей площадью. Выведите данную площадь.
Сайт построен с использованием Pelican. За основу оформления взята тема от Smashing Magazine. Исходные тексты программ, приведённые на этом сайте, распространяются под лицензией GPLv3, все остальные материалы сайта распространяются под лицензией CC-BY.
Конструктор класса – метод __init__
В объектно-ориентированном программировании конструктором класса называют метод, который автоматически вызывается при создании объектов. Его также можно назвать конструктором объектов класса. Имя такого метода обычно регламентируется синтаксисом конкретного языка программирования. Так в Java имя конструктора класса совпадает с именем самого класса. В Python же роль конструктора играет метод __init__ .
В Python наличие пар знаков подчеркивания спереди и сзади в имени метода говорит о том, что он принадлежит к группе методов перегрузки операторов. Если подобные методы определены в классе, то объекты могут участвовать в таких операциях как сложение, вычитание, вызываться как функции и др.
При этом методы перегрузки операторов не надо вызывать по имени. Вызовом для них является сам факт участия объекта в определенной операции. В случае конструктора класса – это операция создания объекта. Так как объект создается в момент вызова класса по имени, то в этот момент вызывается метод __init__ .
Необходимость конструкторов связана с тем, что нередко объекты должны иметь собственные свойства сразу. Пусть имеется класс Person , объекты которого обязательно должны иметь имя и фамилию. Если класс будет описан подобным образом
class Person: def set_name(self, n, s): self.name = n self.surname = s
то создание объекта возможно без полей. Для установки имени и фамилии метод set_name нужно вызывать отдельно:
>>> from test import Person >>> p1 = Person() >>> p1.set_name("Bill", "Ross") >>> p1.name, p1.surname ('Bill', 'Ross')
В свою очередь, конструктор класса не позволит создать объект без обязательных полей:
class Person: def __init__(self, n, s): self.name = n self.surname = s p1 = Person("Sam", "Baker") print(p1.name, p1.surname)
Здесь при вызове класса в круглых скобках передаются значения, которые будут присвоены параметрам метода __init__ . Первый его параметр – self – ссылка на сам только что созданный объект.
Теперь, если мы попытаемся создать объект, не передав ничего в конструктор, то будет возбуждено исключение, и объект не будет создан:
>>> p1 = Person() Traceback (most recent call last): File "", line 1, in TypeError: __init__() missing 2 required positional arguments: 'n' and 's'
Однако бывает, что надо допустить создание объекта, даже если никакие данные в конструктор не передаются. В таком случае параметрам конструктора класса задаются значения по умолчанию:
class Rectangle: def __init__(self, w=0.5, h=1): self.width = w self.height = h def square(self): return self.width * self.height rec1 = Rectangle(5, 2) rec2 = Rectangle() rec3 = Rectangle(3) rec4 = Rectangle(h=4) print(rec1.square()) print(rec2.square()) print(rec3.square()) print(rec4.square())
10 0.5 3 2.0
Если класс вызывается без значений в скобках, то для параметров будут использованы их значения по умолчанию. Однако поля width и height будут у всех объектов.
Кроме того, конструктору вовсе не обязательно принимать какие-либо параметры, не считая self .
В других языка программирования, например в Java, классы могут содержать несколько конструкторов, которые между собой отличаются количеством параметром, а также, возможно, их типом. При создании объекта срабатывает тот конструктор, количество и типы параметров которого совпали с количеством и типами переданных в конструктор аргументов.
В Python создать несколько методов __init__ в классе можно, однако «рабочим» останется только последний. Он переопределит ранее определенные. Поэтому в Python в классах используется только один конструктор, а изменчивость количества передаваемых аргументов настраивается через назначение значений по-умолчанию.
Практическая работа. Конструктор и деструктор
Помимо конструктора объектов в языках программирования есть обратный ему метод – деструктор. Он вызывается, когда объект не создается, а уничтожается.
Это не значит, что сам деструктор уничтожает объект. В теле самого метода нет никаких инструкций по удалению экземпляра. Непосредственное удаление выполняется автоматически так называемым сборщиком мусора.
Деструктор (финализатор) в коде вашего класса следует использовать, когда при удалении объекта необходимо выполнить ряд сопутствующий действий, например, отправить сообщение, закрыть файл, разорвать соединение с базой данных.
В языке программирования Python объект уничтожается, когда исчезают все связанные с ним переменные или им присваивается другое значение, в результате чего связь со старым объектом теряется. Удалить переменную можно с помощью команды языка del . Также все объекты уничтожаются, когда программа завершает свою работу.
В классах Python функцию деструктора выполняет метод __del__ .
Напишите программу по следующему описанию:
- Есть класс Person , конструктор которого принимает три параметра (не учитывая self ) – имя, фамилию и квалификацию специалиста. Квалификация имеет значение заданное по умолчанию, равное единице.
- У класса Person есть метод, который возвращает строку, включающую в себя всю информацию о сотруднике.
- Класс Person содержит деструктор, который выводит на экран фразу «До свидания, мистер …» (вместо троеточия должны выводиться имя и фамилия объекта).
- В основной ветке программы создайте три объекта класса Person . Посмотрите информацию о сотрудниках и увольте самое слабое звено.
- В конце программы добавьте функцию input() , чтобы скрипт не завершился сам, пока не будет нажат Enter . Иначе вы сразу увидите как удаляются все объекты при завершении работы программы.
Курс с примерами решений практических работ:
pdf-версия
X Скрыть Наверх
Объектно-ориентированное программирование на Python
Инициализация и протоколы — Python: Введение в ООП
Теперь, когда вы знакомы со связанными методами, настало время рассказать про самый важный метод в Python: метод __init__ («dunder-init», «дандер инит»). Этот метод отвечает за инициализацию экземпляров класса после их создания.
На прошлых уроках Бобу — экземпляру класса Person , мы задавали имя уже после того, как сам объект был создан. Такое заполнение атрибутов объекта и выглядит громоздко и может приводить к разного рода ошибкам: объект может какое-то время находиться в «недозаполненном» (более общее название — «неконсистентном») состоянии. Инициализатор же позволяет получить уже полностью настроенный экземпляр.
Реализуем класс Person так, чтобы имя можно было указывать при создании объекта:
class Person: def __init__(self, name): self.name = name bob = Person('Bob') bob.name # 'Bob' alice = Person('Alice') alice.name # 'Alice'
Теперь нет нужды иметь в классе атрибут name = ‘Noname’ , ведь все объекты получают имена при инстанцировании!
Методы и протоколы
Вы заметили, что Python сам вызывает метод __init__ в нужный момент? Это же касается большинства dunder-методов: таковые вы только объявляете, но вручную не вызываете. Такое поведение часто называют протоколом (или поведением): класс реализует некий протокол, предоставляя нужные dunder-методы. А Python, в свою очередь, работает с объектом посредством протокола.
Продемонстрирую протокол получения длины имени объекта:
class Person: def __init__(self, name): self.name = name def __len__(self): return len(self.name) tom = Person('Thomas') len(tom) # 6
Я объявил метод dunder-len, и объекты класса Person научились возвращать «свою длину» (да, пример надуманный, но суть отражает). Вызов функции len на самом деле приводит к вызову метода __len__ у переданного в аргументе объекта!
Протоколы и «утиная типизация»
Существует такой термин: «утиная типизация» («duck typing») — «Если что-то крякает как утка и плавает как утка, то возможно это утка и есть!». Данный термин, пусть и звучит как шутка, описывает важный принцип построения абстракций: коду практически всегда достаточно знать о значении, с которым этот код работает, что это значение обладает нужными свойствами. Все остальное — не существенно.
Если коду достаточно знать, что сущность умеет плавать и крякать, то код не должен проверять сущность на фактическую принадлежность к уткам. Это позволит коду работать с робо-утками, даже если все настоящие утки вымрут, ведь код работает с абстрактными утками!
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
ООП в Python
Цель у этих подходов одна — сделать процесс программирования максимально эффективным. Но в ООП, в отличии от процедурного подхода, данные первичны, а код для обработки этих данных — вторичен.
- В процедурном подходе основой программы является функция. Функции вызывают друг друга и при необходимости передают данные. В программе функции живут отдельно, данные — отдельно.
- Основной недостаток процедурного подхода — сложность создания и поддержки больших программ. Наличие сотен функций в таких проектах очень часто приводит к ошибкам и спагетти-коду.
- В основе объектно-ориентированного программирования лежит понятие объекта. Объект совмещает в себе и функции и данные.
- Основное преимущество ООП перед процедурным программированием — изоляция кода на уровне классов, что позволяет писать более простой и лаконичный код. 1
Классы и объекты в ООП
Объектно-ориентированное программирование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса, а классы образуют иерархию наследования. 2
Класс
- описывает множество объектов, имеющих общую структуру и обладающих одинаковым поведением
- это шаблон кода, по которому создаются объекты. Т. е. сам по себе класс ничего не делает, но с его помощью можно создать объект и уже его использовать в работе
- Классы в Python – это тоже объекты
- Допустимо динамическое изменение и добавление атрибутов классов
- Для скрытия внутренних данных используются синтаксические соглашения
- Поддерживается наследование
- Полиморфизм обеспечивается виртуальностью всех методов
- Доступно метапрограммирование
Объект (экземпляр класса)
- это конкретный представитель класса
- Жизненным циклом объекта можно управлять
- Многие операторы могут быть перезагружены
- Многие методы встроенных объектов можно эмулировать
Атрибуты класса (Attributes)
Атрибут класса (объекта) — любой элемент (свойство, метод, подкласс), на который можно сослаться через символ точки ( MyClass. или my_object. ).
Атрибуты делятся на встроенные и пользовательские:
- Встроенные (служебные) атрибуты — методы и свойства унаследованные от общего для всех классов в Python родительского класса object . Многие из этих атрибутов можно переопределить внутри своего класса.
- Пользовательские атрибуты — поля и методы, которые описываются программистом в теле класса. Добавляются в общий список атрибутов наряду со встроенными. 3
- Поля класса — это характеристики объекта класса.
- Методы класса — это функции, с помощью которых можно оперировать данными класса.
Любой метод является атрибутом, но не любой атрибут — методом.
Атрибуты-поля можно условно разделить на две группы:
- Статические — поля класса, которые объявляются внутри тела класса и создаются тогда, когда создается класс.
- Динамические — поля экземпляра. Для создания динамического поля необходимо обратиться к self внутри метода.
Служебное слово self — это ссылка на текущий экземпляр класса. self не является зарезервированным. Является аналогом этого this (Java, C++).
Встроенные (специальные) атрибуты
Атрибуты (поля и методы), имена которых обрамляются __ , Python трактует как специальные. Специальные атрибуты, как правило, идут первыми при объявлении класса.
- операторы перегрузки — если необходимо добавить возможность выполнения стандартных операций над классами
- дополнить…
Основные встроенные методы и поля:
__new__(cls, . ) — Конструктор. Создает экземпляр класса. Сам класс передается в качестве аргумента. Редко переопределяется, чаще используется реализация от базового класса object
__init__(self, . ) — Инициализатор. Принимает свежесозданный объект класса из конструктора. Является очень удобным способом задать параметры объекта при его создании.
__del__(self) — Деструктор. Вызывается при удалении объекта сборщиком мусора
__str__(self) — Возвращает строковое представление объекта.
__hash__(self) — Возвращает хэш-сумму объекта.
__doc__ — Тип: str . Документация класса.
__dict__ — Тип: dict . Словарь, в котором хранится пространство имен класса
Свойства класса (@property)
Организация доступа к членам класса в Python построена на принципе универсального доступа, гласящем, что «все услуги, предлагаемые модулем должны быть доступны через единую нотацию, которая не раскрывает, реализованы ли они посредством хранения либо вычисления».
В частности, это предполагает
- предоставлять доступ к переменным напрямую, например, foo.x = 0 , а не foo.set_x(0) ;
- в случае необходимости проверки устанавливаемого значения использовать свойства, которые сохраняют единый синтаксис доступа, установка значения foo.x = 0 приводит к вызову foo.set_x(0) .
Преимуществом данного подхода является возможность использование синтаксиса foo.x += 1 , хотя на самом деле внутри происходит вызов foo.set_x(foo.get_x() + 1) .
Свойства (Property) — это особый вид атрибутов имитирующий поле (но который при чтении вызывает какой-либо метод).
- У них есть методы получения, установки и удаления, такие как __get__ , __set__ и __delete__
- Мы можем определить геттеры, сеттеры и деструкторы с помощью функции property()
- Свойство может определяться при помощи декораторов: @property — определяет метод получения значения, @field.setter — определяет метод установки значения свойства field . Имя свойства field определяется в наименовании обоих методов и декораторе @field.setter
- Если необходимо реализовать свойство «только для чтения», второй метод может быть опущен вместе с декоратором @field.setter
- Примером применения свойства является получение информации, которая может потребовать затратного первоначального поиска и простого повторного
По сути, когда Python встречает следующий код:
он ищет eggs в spam , а затем проверяет eggs на наличие у него методов __get__ , __set__ или __delete__ и если они есть, то это свойство. Если это свойство, то вместо того, чтобы просто вернуть объект eggs (как это было бы для любого другого атрибута), он вызовет метод __get__ и возвращает все, что возвращает этот метод. 4
Методы экземпляра (обычные методы)
Методы экземпляра — это обычные функции, которые становятся доступны только после создания экземпляра класса. Первым параметром такого метода является слово self .
Статические методы (Static methods)
Статические методы — это обычные функции, которые помещены в класс для удобства и тем самым располагаются в области видимости этого класса. Чаще всего это какой-то вспомогательный код.
Обозначаются специальным декоратором @staticmethod .
- ничего не знают о классе или об объекте, на котором они вызываются
- не принимают специальных аргументов типа self или cls поэтому не используют сам объект или класс при выполнении
- могут быть вызваны, как через сам класс, так и через его экземпляр
Методы класса (Class methods)
Методы класса принимают в качестве первого параметра cls (вместо self в обычных методах). cls — это ссылка на класс, на котором был вызван метод.
Обозначаются специальным декоратором @classmethod .
- могут менять состояние самого класса, что в свою очередь отражается на ВСЕХ экземплярах данного класса
- не могут менять конкретный объект класса
- используются, когда не требуется привязка к экземпляру объекта
- привязаны только к области видимости
Методы класса часто используются, когда:
- Необходимо создать специфичный объект текущего класса
- Нужно реализовать фабричный паттерн (создаём объекты различных унаследованных классов прямо внутри метода)
- Реализовать дополнительные методы инициализации
Перегрузка методов (Method Overloading)
Перегрузка методов (Множественная диспетчеризация, Мультиметоды) — это использование множества методов с одним и тем же именем, которые позволяют выбирать нужную функциональность в зависимости от количества, типов или значений аргументов в пределах одного класса.
Python по умолчанию не поддерживает перегрузку методов, поскольку запоминает только самое последнее определение метода. Для их реализации необходимо подключать сторонние Python библиотеки, например, multimethods.py .
Переопределение методов (Method Overriding)
Когда метод с тем же именем и аргументами используется как в производном классе, так и в базовом или суперклассе, мы говорим, что метод производного класса переопределяет метод, представленный в базовом классе.
Последовательность поиска атрибутов
У одного объекта может быть несколько родительских классов, а также специальные методы вроде __getattribute__ , которые перехватывают запросы к атрибутам.
Каким же образом интерпретатор разрешает сложные запросы к свойствам и методам? Рассмотрим последовательность поиска на примере запроса obj.field :
- Вызов obj.__getattribute__(‘field’) , если он определен. При установке или удалении атрибута проверяется соответственно наличие __setattr__ или __delattr__ .
- Поиск в obj.__dict__ (пользовательские атрибуты).
- Поиск в object.__class__.__slots__ .
- Рекурсивный поиск в поле __dict__ всех родительских классов. Если класс имеет несколько предков, порядок проверки соответствует порядку их перечисления в определении.
- Если определен метод __getattr__ , то происходит вызов obj.__getattr__(‘field’)
- Выбрасывается исключение несуществующего атрибута – AttributeError .
Наконец, когда атрибут нашелся, проверяется наличие метода __get__ (при установке – __set__ , при удалении – __delete__ ).
Все эти проверки совершаются только для пользовательских атрибутов. 5
Класс как структура данных
В ряде случаев бывает полезным иметь структуру, похожую на структуру из языка Си (или запись из Паскаля), позволяющую логически сгруппировать данные. Для этого можно использовать словарь или класс с пустой реализацией.
Основные принципы ООП
На текущий момент ООП является самой востребованной и распространенной парадигмой программирования. Концепция ООП строится на основе 4 принципов: абстракция, инкапсуляция, наследование и полиморфизм.
Абстракция
Абстракция — принцип ООП, согласно которому объект характеризуется свойствами, которые отличают его от всех остальных объектов и при этом четко определяют его концептуальные границы.
Абстракция позволяет представить сложную концепцию в более простой форме:
- Выделить главные и наиболее значимые свойства предмета.
- Отбросить второстепенные характеристики.
Абстракция не поддерживается в Python напрямую.
Инкапсуляция
Инкапсуляция — принцип ООП, согласно которому сложность реализации программного компонента должна быть спрятана за его интерфейсом.
Инкапсуляция не дает взглянуть на внутреннюю реализацию сложной концепции:
- Отсутствует доступ к внутреннему устройству программного компонента.
- Взаимодействие компонента с внешним миром осуществляется посредством интерфейса, который включает публичные методы и поля.
В ряде языков, например, С++, существует четкое разделение членов класса на закрытые ( private ), защищенные ( protected ) и публичные ( public ).
В Python все члены класса являются общедоступными, но существует возможность эмуляции private и protected на уровне договоренностей.
Концепция отсутствия закрытых атрибутов в Python описывается фразой одного из разработчиков языка: «Мы все взрослые люди. Если программист хочет выстрелить себе в ногу — нужно предоставить ему возможность это сделать».
В Python принята следующая договоренность:
Protected (Non-Public) — обозначается при помощи одинарного нижнего подчеркивания _ . Данный синтаксис указывает на то, что атрибут:
- используется для внутренней реализации класса и не предназначен для использования извне;
- должен быть использован/изменен только если разработчик-пользователь класса абсолютно уверен в этом.
- При этом атрибут с _ доступен извне, как и обычный public-атрибут класса.
Private — обозначается при помощи двойного нижнего подчеркивания __ . Данный синтаксис указывает на то, что атрибут:
- используется для внутренней реализации класса и не предназначен для использования извне;
- не должен быть использован/изменен разработчиком-пользователем класса.
- При этом атрибут с __ оказывается недоступным извне, используя технику сокрытия имен (Name Mangling). Несмотря на это, в отличие от ряда языков (например, Java) такие «закрытые» члены класса также можно изменять, но более сложным способом — их можно увидеть, используя функцию dir() .
We don’t use the term “private” here, since no attribute is really private in Python (without a generally unnecessary amount of work). 6
>I'm Public >I'm Protected >I'm Private >Private method
Геттеры, сеттеры и деструкторы
Кроме прямого доступа к атрибутам ( obj.attrName ), могут быть использованы специальные методы доступа: геттеры, сеттеры и деструкторы:
Такой подход очень удобен, если получение или установка значения атрибута требует сложной логики.
Вместо того чтобы вручную создавать геттеры и сеттеры для каждого атрибута, можно перегрузить встроенные методы __getattr__ , __setattr__ и __delattr__ . Например, так можно перехватить обращение к свойствам и методам, которых в объекте не существует:
__getattribute__ перехватывает все обращения (в том числе и к существующим атрибутам).
Для чего нужна инкапсуляция?
- Инкапсуляция упрощает процесс разработки, т. к. позволяет нам не вникать в тонкости реализации того или иного объекта.
- Повышается надежность программ за счет того, что при внесении изменений в один из компонентов, остальные части программы остаются неизменными.
- Становится более легким обмен компонентами между программами.
Наследование
Наследование — способ создания нового класса на основе уже существующего, при котором класс-потомок заимствует свойства и методы родительского класса, а также добавляет собственные.
- Класс потомок может переопределять родительские методы.
- При этом, обычно, дочерний класс дополняет родительский метод, добавив свой код после кода родителя (используя функцию super() , предоставляющую ссылку на родительский класс).
- Каждый класс также может получить информацию о своих «родителях» через метод __bases__() или isinstance() .
- Наследование описывается словом «является» (легковой автомобиль является автомобилем).
- Существуют и другой вид взаимосвязи (модель включения/делегации), когда один класс включает в себя другой класс в качестве одного из полей — ассоциация, композиция и агрегация. Ассоциация описывается словом «имеет» (автомобиль имеет двигатель).
Метод super() дает возможность наследнику обратиться к родительскому классу.
Для чего нужно наследование?
- Принцип DRY (повторное использование кода);
- Классы-потомки берут общий функционал у родительского класса.
- Ускорение разработки нового ПО на основе переиспользования существующих открытых классов.
- Наследование упрощает процесс написания кода.
Python реализует как стандартное одиночное наследование так и множественное.
Множественное наследование
Используя множественное наследования можно создавать классы-миксины (примеси), представляющие собой определенную особенность поведения.
Множественное наследование часто критикуется 10 и зачастую считается признаком неверного анализа и проектирования, поэтому его использование рекомендуется в случае крайней необходимости и оправданности такого решения. 7
Полиморфизм
Термин «полиморфизм» происходит из греческого языка и означает «нечто, что принимает несколько форм».
Полиморфизм — это поддержка нескольких реализаций на основе общего интерфейса.
Абстрактный метод (виртуальный метод) — это метод класса, реализация для которого отсутствует.
Полиморфизм в компилируемых языках
В компилируемых языках программирования полиморфизм достигается за счет создания виртуальных методов, которые в отличие от невиртуальных можно перегрузить в классе-потомке.
Все методы в языке изначально виртуальные. Это значит, что дочерние классы могут их переопределять и решать одну и ту же задачу разными путями. Название метода остается прежним, а реализация изменяется и будет выбрана только во время исполнения программы. Такие классы называют полиморфными.
Другая формулировка
Полиморфизм позволяет одинаково обращаться с объектами, имеющими однотипный интерфейс, независимо от внутренней реализации объекта.
Можно получить и доступ к методам класса-предка либо по прямому обращению, либо с помощью функции super() :
Одинаковый интерфейс с разной реализацией могут иметь и классы, которые не связаны отношениями Родитель-Потомок. Это возможно благодаря утиной типизации.
Метаклассы
Метаклассы – это классы, инстансы которых тоже являются классами. 8
- Принципы ООП. Классы, объекты, поля и методы. Уровни доступа.↩︎
- Wikipedia — ООП↩︎
- Пользовательские атрибуты в Python↩︎
- What’s the difference between a Python “property” and “attribute”?↩︎
- ООП на Python: концепции, принципы и примеры реализации↩︎
- PEP 8 — Designing for Inheritance↩︎
- Объектно-ориентированное программирование и классы↩︎
- Метаклассы в Python: что это такое и с чем его едят↩︎