Объектная модель Python
Объектно-ориентированное_программирование: Поддержка в ЯП объектного проектирования.
- Не «парадигма» (в строгом смысле)
- Не привилегия ЯП (можно писать ООП-программы на Си)
Статья с Википедии вполне философская, почитайте её ☺, она вообще про управление!
Три составляющих ООП:
Абстракция (общее/частное).
- Примеры «вертикальной» абстракции: класс/экземпляр класса, базовый класс/производный класс и т. п. (Python: +метакласс)
- Примеры «горизонтальной» абстракции моделирование различных сущностей предметной области одинаковыми структурами ЯП
Инкапсуляция: минимализация необходимого информационно пространства; часто — за счёт создания иерархии пространств имён (большинство языков: размещение в одном компоненте данных и методов, которые с ними работают)
- не смешивать с сокрытием
Наследование: повторное использование свойств объектов с описанием различий
Полиморфизм (а вот эту статью на Википедии не стоит читать на полном серьёзе): возможность для одного и того же кода обрабатывать данные разных типов (Python: duck typing!)
Следующий подраздел — отдельная статья про классы. Обещанную вторую я так и не написал, если вдруг кто-то считает, что она нужна, мотивируйте меня ☺.
Статья «Классы в Python3 — это очень просто»
Примечание: достаточно сделать все примеры, и наступит Просветление! Если оно не наступило, прочитать комментарии к примерам. Если всё равно непонятно, пишите письма автору — значит, не везде удалось объяснить достаточно прозрачно.
Поля классов и объектов
Стандартные объекты — например, кортежи — рождаются с полями:
1 >>> l=()
2 >>> dir(l)
3 ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index']
4 >>> l.new="QQ"
5 Traceback (most recent call last):
6 File "<stdin>", line 1, in <module>
7 AttributeError: 'tuple' object has no attribute 'new'
Этих полей довольно много, но все они какие-то служебные и нам сейчас не интересны. А впихивать новые поля в стандартный объект нельзя.
Но можно самому создать объект. Для этого надо с помощью оператора class описать конструктор этого объекта, а потом вызвать этот конструктор. Он вернёт объект — ровно такой, как заказывали. Например, пустой:
1 >>> class C:
2 ... pass
3 ...
4 >>> dir(C)
5 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
6 >>> c=C()
7 >>> dir(c)
8 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
9 >>> c
10 <__main__.C object at 0x7f39429f92b0>
11 >>> C
12 <class '__main__.C'>
Постой-постой, он не совсем пустой! В нём тоже есть несколько каких-то служебных имён, ну да это мелочи, главное — неслужебных нет. Итак, C — конструктор объекта, а c — сконструированный им объект (или «экземпляр», instance).
И вот в такой объект можно самому напихать какие хочешь поля:
Поля, добавленные в объект, в самом классе не появляются. Но и в сам класс (конструктор) тоже можно напихать какие хочешь поля:
Более того, эти поля тут же становятся видны видны во всех экземплярах. Если в самом экземпляре поля нет, Python смотрит, а нет ли его в классе.
А если такое поле в объекте создать, оно «заслоняет» собой поле класса (как локальная переменная заслоняет глобальную):
Поле объекта можно удалить! Тогда снова станет видимым поле класса:
Поле класса можно удалить, разумеется, только в самом классе:
1 >>> del d.strange
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 AttributeError: strange
5 >>> dir(d)
6 ['__class__', …, '__weakref__', 'strange']
7 >>> del C.strange
8 >>> d.strange
9 Traceback (most recent call last):
10 File "<stdin>", line 1, in <module>
11 AttributeError: 'C' object has no attribute 'strange'
12 >>> dir(d)
13 ['__class__', …]
14 >>> dir(C)
15 ['__class__', …]
Задание полей в конструкторе, часть первая
Если мы хотим, чтобы при создании объекта в нём уже были какие-то поля, эти поля надо добавить в класс. Только вот делать это в виде класс.поле = значение там и сям в коде программы как-то… неструктурно. Можно сделать так:
Теперь при обращении к полям w и h любого объекта типа D («экземпляра класса D») мы получим какое-нибудь значение — либо поля из класса, если мы ничего не добавляли в объект, либо поля объекта, если добавляли:
Тут есть одна неприятность. Если поле класса — не константа (например, список), и его можно менять, то пока мы его не переопределили в объекте, мы изменяем поле класса:
А как только мы зададим поле объекта, мы начинаем изменять поле объекта:
Та же история, что с глобальными и локальными переменными. Не было присваивания — видно поле класса, присвоили — трах! — появилось поле объекта. И за этим надо следить. Запомним на будущее.
Методы
Разумеется, поля объекта могут быть абсолютно любого типа. Числами. Строками. Функциями. Экземплярами класса. Классами. Любого типа.
Функциями — это хорошо! Вот, например, функция, которая показывает значение поля класса:
Поле класса — это не так интересно, как поле объекта. Нам же их в основном менять. А для того, чтобы добыть поле объекта, нужно знать сам объект. Выходит, чтобы добавлять функцию в объект, нужно сначала создать объект, запоминать и никогда не трогать ссылку на этот объект, потом каждый раз создавать новую функцию, в которой использовать эту неприкосновенную ссылку? Ужас.
А нельзя ли прямо в конструктор функцию записать? В класс? Чтобы она всегда там уже была? А ссылку на объект — что же, передадим и ссылку, жалко, что ли? Вот так как-нибудь:
Час от часу не легче! Как это так «задано два параметра»?! Один же!
Посмотрим. Передадим один параметр, а в функции напишем, что их два, раз уж оно так думает:
Ах во-о-о-т как. Если функцию, определённую внутри класса, вызвать из экземпляра, Python передаёт ей на один параметр больше! При вызове вида объект.функция(параметр1, …) на самом деле вызывается класс.функция(объект, параметр1, …). Такой способ вызова превращает функцию в метод.
Собственно, Python как бы намекает нам на это:
1 >>> class I:
2 ... def meth():
3 ... pass
4 ...
5 >>> def fun():
6 ... pass
7 ...
8 >>> I.subfun = fun
9 >>> i = I()
10 >>> fun
11 <function fun at 0x7feffab441e0>
12 >>> I.subfun
13 <function fun at 0x7feffab441e0>
14 >>> I.meth
15 <function I.meth at 0x7feffab61048>
16 >>> i.subfun
17 <bound method fun of <__main__.I object at 0x7feffab604a8>>
18 >>> i.meth
19 <bound method I.meth of <__main__.I object at 0x7feffab604a8>>
Да. То есть функция вообще ничем не отличается от метода класса. Кроме имени, в котором отложилось имя исходного класса. Но метод экземпляра, оказывается — совсем другой объект! Очевидно, каким бы путём функция ни попала в поля класса, она становится методом, если вызвать её из экземпляра.
Ну-ка, ну-ка:
Вуаля! Осталось только заметить, чтоб для удобства чтения этот первый параметр принято называть self.
Инициализация
Вернёмся теперь к чуду превращения полей класса в поля объекта. Это плохое, негодное чудо: за ним надо следить. Например, создать объект и тут же заполнить все его поля. Тогда они все будут полями объекта, и следить больше будет не за чем.
Ну так вот, если задать специальный метод __init__(), конструктор его вызовет в процессе создания объекта (точнее — сразу после создания, но перед тем, как отдавать в программу результат). Там-то можно все поля свежесозданного, но незаполненного ещё объекта и заполнить.
Более того, если передать какие-то параметры конструктору, он их передаст в __init__() (разумеется, вместе со ссылкой на объект в качестве первого параметра):
Вот.
До встречи в прямом эфире! Не пропустите следующую серию, в которой класс G скажет:
— Всему самому лучшему, что у меня есть, я обязан родителю! Я всё унаследовал от него — и эти поля, и эти методы — а сам лишь кое-что улучшил и добавил немного от себя.
Конец статьи
Коротко
- Класс — это пространство имён
Класс — это изготовитель (чтобы не сказать «конструктор») объекта — экземпляра класса, т. е. такой callable.
У класса (объекта) есть поля (иногда говорят атрибуты, сейчас поймёте, почему ☺). Callable-поля называются методы, не-callable называются, извините, поля.
- Все поля класса видны в объекте
До тех пор, пока их не загородят уникальные поля объекта с теми же именами (аналог: глобальные и локальные переменные)
При этом функции класса превращаются в методы объекта
- Метод — это такая функция внутри пространства имён объекта, при вызове которой в начало кортежа параметров ставится сам объект
Специальный метод __init__() нужен для создания полей объекта и вообще логики его создания
Это не конструктор, это именно инициализатор уже сознанного объекта!
(конструктор тоже есть, это __new__(), но он хитрый, нужен редко, и требует знаний о метаклассах, это потом)
Строка документации класса (и методов, разумеется, но там как в функциях).
Перегрузка операций
Нет никакой перегрузки операций, да она и не нужна! (Строго говоря — есть, и нужна, но редко и по специальному поводу — это тема двух будущих лекций).
На самом деле всё наоборот — нет никаких операций, есть синтаксический сахар и протоколы над методами.
Даже ложкиоперации «.» нет:
.__getattr__() — вызывается, если нельзя найти поле обычным путём
1 >>> from collections import Counter 2 class Geta: 3 acc = Counter() 4 def __getattr__(self, attr): 5 self.acc[attr] += 1 6 return self.acc[attr] 7 >>> g = Geta() 8 >>> g.acc 9 Counter() 10 >>> g.qwe 11 1 12 >>> g.acc 13 Counter({'qwe': 1}) 14 >>> g.qwe 15 2 16 >>> g.acc 17 Counter({'qwe': 2}) 18 >>> [g.rty for i in range(5)] 19 [1, 2, 3, 4, 5] 20 >>> g.acc 21 Counter({'rty': 5, 'qwe': 2})
.__getattribute__() — если есть, вызывается всегда; обычные поля при этом придётся доставать так:
object.__getattribute__(self, "поле") (обратите внимание на object, это именно он!)
1 >>> from collections import Counter 2 2 class Geta: 3 3 acc = Counter() 4 4 def __getattribute__(self, attr): 5 5 acc = object.__getattribute__(self, "acc") 6 6 acc[attr] += 1 7 7 return acc[attr] 8 8 g = Geta() 9 >>> g.qwe 10 1 11 >>> g.qwe 12 2 13 >>> g.qwe 14 3 15 >>> g.acc 16 1 17 >>> g.acc 18 2
.__setattr__() — если есть, вызывается всегда
обычные поля при этом придётся модифицировать только так: object.__setattr__(self, "поле", значение)
- Пример:
.__delattr__()
.__del__() — это не деструктор, это именно метод, вызываемый перед актуальным удалением объекта
.__dir__() — а вы что думали, там просто словарь лежит? Нет, ну он лежит, конечно, но можно и так ☺
Спецметоды
.__что-то__()
- Только в классе (нельзя подменить в объекте)
Нельзя подменить с помощью .__[sg]etattr*
Примеры (сколько успеем)
.__str__() и .__repr__()
len()
iter()
.__bool__()
Например, + (.__add__() и .__radd__())
NotImplemented
В частности, операция @ (__matmul__()), которой нет ни у одного стандартного типа
.__getitem__()/.__setitem__() — индексирование и секционирование
- параметр — любой объект!
slice, если есть : (в действительности все три индекса — любой объект!)
... в параметрах — объект Ellipsis (он же ...) — также не поддерживается ни одним стандартным типом
.__call__() — объект можно вызвать как функцию
- …
Д/З
Прочитать про классы в tutorial; про «волшебные методы» например, тут
Прочитать про ограничения на перегрузку спецметодов в справочнике
Вообще выборочно почитать справочник по этой теме. Там много такого, про что в лекциях мы поговорить не успеем.
EJudge: DummyVec 'Что-то вроде вектора'
Написать класс vector, представляющий нечто, похожее на вектор. Должна поддерживаться операция вывода в формате, представленном в примере, конструирование из произвольной числовой последовательности ненулевой длины, а также сложение и скалярное произведение с числовой последовательностью такой же длины (в том числе с другим vector). Скалярное произведение задаётся символом «@».
2:1:2:1:2:1:2 0:1:2:3:4:5:6 4:3:6:5:8:7:10 0:2:4:6:8:10:12 0:2:4:6:8:10:12 143
EJudge: DahDit 'Морзянка'
Написать класс morse("строка"), экземпляр которого переводит арифметические выражения в морзянку! В выражении «+» — это точка, «-» — тире, а «~» — промежуток между буквами (бывает только между буквами и только один, проверять не надо).
- Параметр — строка, состоящая либо из символов, либо из слов.
- Строка состоит из слов, если в ней есть хотя бы один пробел (в этом случае между словами стоит ровно один пробел)
- Если в строке три элемента, они задают точку, точку на конце передаваемой буквы (традиционно обозначается другим слогом) и тире
Два элемента задают точку (она же точка на конце буквы) и тире
- Если элемента четыре, четвёртый — это то, что выводится в конце сообщения
- По умолчанию:
Если параметров нет, это слова "di", "dit" и "dah".
Если параметры — слова: в конце сообщения выводится ".", разделители при выводе: пробел между сигналами и ", " между буквами.
- Если параметры — символы: непуст только разделитель между буквами (это пробел).
1 print(-+morse()) 2 print(-++~+-+morse()) 3 print(--+~-~-++~+++-morse()) 4 print(--+~-~-++~+++-morse(".-")) 5 print(--+~-~-++~+++-morse("..-")) 6 print(--+~-~-++~+++-morse("..-|")) 7 print(--+~-~-++~+++-morse("dot DOT dash")) 8 print(--+~-~-++~+++-morse("ai aui oi ")) 9 print(--+~-~-++~+++-morse("dot dot dash ///"))
dah dit. dah di dit, di dah dit. dah dah dit, dah, dah di dit, di di di dah. --. - -.. ...- --. - -.. ...- --. - -.. ...-| dash dash DOT, dash, dash dot DOT, dot dot dot dash. oi oi aui, oi, oi ai aui, ai ai ai oi dash dash dot, dash, dash dot dot, dot dot dot dash///
EJudge: MixNamespace 'Смешение языцей'
Написать функцию mix(…) от произвольного количества параметров-объектов. Функция воспринимает объекты как инкапсулированные пространства имён и возвращает объект, содержащий объединение полей всех объектов-параметров. Поля, чьи имена начинаются на "_", и все callable-поля (методы и функции) отбрасываются. Если в некоторых объектах поля имеют одинаковые имена, используется значение последнего из параметров. Дополнительно у возвращаемого объекта переопределяется __str__(): она возвращает строку вида "поле=значение, поле=значение, …", в которой имена полей отсортированы в алфавитном порядке.
denominator=1, imag=100500, numerator=6, real=6, start=3, step=5, stop=4 1 100500 6 6 3 5 4
EJudge: CyberSausage 'Киберколбаса'
Написать класс Sausage, имитирующий киберколбасу. Киберколбаса может быть проинициализирована нулём значений (создаётся один батон с фаршем "pork!"), одним (фарш типа str) и двумя (фарш, и объём типа Fraction). Длина целого батона киберколбасы — 12 символов фарша и 2 символа оболочки. Колбаса единичного объёма — это один полный батон, более, чем единичного — это несколько батонов (последний, возможно, неполон). Неполный батон заканчивается срезом. Киберколбаса поддерживает операции умножения и деления на целое неотрицательное число, сложение и вычитание с другой киберколбасой (фарш результата совпадает с фаршем первого операнда), а также взятие абсолютного значения (возвращается объём). Отрицательного объёма не бывает, в этом случае он делается нулевым. Если объём киберколбасы нулевой, батон считается пустым. При выводе округлять двенадцатые доли батона в сторону ближайшего меньшего.
/------------\ |pork!pork!po| |pork!pork!po| |pork!pork!po| \------------/ /----------| |HAMHAMHAMH| |HAMHAMHAMH| |HAMHAMHAMH| \----------| /------------\/---| |SPAM.SPAM.SP||SPA| |SPAM.SPAM.SP||SPA| |SPAM.SPAM.SP||SPA| \------------/\---| /------------\/------------\/------------\/-| |pork!pork!po||pork!pork!po||pork!pork!po||p| |pork!pork!po||pork!pork!po||pork!pork!po||p| |pork!pork!po||pork!pork!po||pork!pork!po||p| \------------/\------------/\------------/\-| 37/12 /------------\/--------| |HAMHAMHAMHAM||HAMHAMHA| |HAMHAMHAMHAM||HAMHAMHA| |HAMHAMHAMHAM||HAMHAMHA| \------------/\--------| /------------\ |SPAM.SPAM.SP| |SPAM.SPAM.SP| |SPAM.SPAM.SP| \------------/ /| || || || \| False 3/64 /| || || || \| True 0