Объектно-ориентированное программирование. Специальные методы.
Декоратор @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.
Как создать инициализатор класса someclass

Комментарии
Популярные По порядку
Не удалось загрузить комментарии.
ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ
13 лучших книг по Python для начинающих и продолжающих
Представляем вам подборку лучших книг по Python для начинающих программистов, опытных питонистов и детей, с реальными рецензиями.
3 самых важных сферы применения Python: возможности языка
Существует множество областей применения Python, но в некоторых он особенно хорош. Разбираемся, что же можно делать на этом ЯП.
Программирование на Python: от новичка до профессионала
Пошаговая инструкция для всех, кто хочет изучить программирование на 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 студентов
Наши выпускники работают в компаниях:
Руководство по магическим методам в Питоне
Это перевод 1.17 версии руководства от Rafe Kettler.
Вступление
Что такое магические методы? Они всё в объектно-ориентированном Питоне. Это специальные методы, с помощью которых вы можете добавить в ваши классы «магию». Они всегда обрамлены двумя нижними подчеркиваниями (например, __init__ или __lt__ ). Ещё, они не так хорошо документированны, как хотелось бы. Все магические методы описаны в документации, но весьма беспорядочно и почти безо всякой организации. Поэтому, чтобы исправить то, что я воспринимаю как недостаток документации Питона, я собираюсь предоставить больше информации о магических методах, написанной на понятном языке и обильно снабжённой примерами. Надеюсь, это руководство вам понравится. Используйте его как обучающий материал, памятку или полное описание. Я просто постарался как можно понятнее описать магические методы.
Конструирование и инициализация.
Всем известен самый базовый магический метод, __init__ . С его помощью мы можем инициализировать объект. Однако, когда я пишу x = SomeClass() , __init__ не самое первое, что вызывается. На самом деле, экземпляр объекта создаёт метод __new__ , а затем аргументы передаются в инициализатор. На другом конце жизненного цикла объекта находится метод __del__ . Давайте подробнее рассмотрим эти три магических метода:
-
__new__(cls, [. )
Это первый метод, который будет вызван при инициализации объекта. Он принимает в качестве параметров класс и потом любые другие аргументы, которые будут переданы в __init__ . __new__ используется весьма редко, но иногда бывает полезен, в частности, когда класс наследуется от неизменяемого (immutable) типа, такого как кортеж (tuple) или строка. Я не намерен очень детально останавливаться на __new__ , так как он не то чтобы очень часто нужен, но этот метод очень хорошо и детально описан в документации.
Замечание от переводчика: svetlov отмечает, что здесь автор ошибается, на самом деле __del__ всегда вызывается по завершении работы интерпретатора.
from os.path import join class FileObject: '''Обёртка для файлового объекта, чтобы быть уверенным в том, что файл будет закрыт при удалении.''' def __init__(self, filepath='~', filename='sample.txt'): # открыть файл filename в filepath в режиме чтения и записи self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file
Переопределение операторов на произвольных классах
Одно из больших преимуществ использования магических методов в Питоне то, что они предоставляют простой способ заставить объекты вести себя по подобию встроенных типов. Это означает, что вы можете избежать унылого, нелогичного и нестандартного поведения базовых операторов. В некоторых языках обычное явление писать как-нибудь так:
if instance.equals(other_instance): # do something
Вы, конечно, можете поступать так же и в Питоне, но это добавляет путаницы и ненужной многословности. Разные библиотеки могут по разному называть одни и те же операции, заставляя использующего их программиста совершать больше действий, чем необходимо. Используя силу магических методов, мы можем определить нужный метод ( __eq__ , в этом случае), и так точно выразить, что мы имели в виду:
if instance == other_instance: #do something
Это одна из сильных сторон магических методов. Подавляющее большинство из них позволяют определить, что будут делать стандартные операторы, так что мы можем использовать операторы на своих классах так, как будто они встроенные типы.
Магические методы сравнения
В Питоне уйма магических методов, созданных для определения интуитивного сравнения между объектами используя операторы, а не неуклюжие методы. Кроме того, они предоставляют способ переопределить поведение Питона по-умолчанию для сравнения объектов (по ссылке). Вот список этих методов и что они делают:
-
__cmp__(self, other)
Самый базовый из методов сравнения. Он, в действительности, определяет поведение для всех операторов сравнения (>, ==, !=, итд.), но не всегда так, как вам это нужно (например, если эквивалентность двух экземпляров определяется по одному критерию, а то что один больше другого по какому-нибудь другому). __cmp__ должен вернуть отрицательное число, если self < other , ноль, если self == other , и положительное число в случае self >other . Но, обычно, лучше определить каждое сравнение, которое вам нужно, чем определять их всех в __cmp__ . Но __cmp__ может быть хорошим способом избежать повторений и увеличить ясность, когда все необходимые сравнения оперерируют одним критерием.
class Word(str): '''Класс для слов, определяющий сравнение по длине слов.''' def __new__(cls, word): # Мы должны использовать __new__, так как тип str неизменяемый # и мы должны инициализировать его раньше (при создании) if ' ' in word: print "Value contains spaces. Truncating to first space." word = word[:word.index(' ')] # Теперь Word это все символы до первого пробела return str.__new__(cls, word) def __gt__(self, other): return len(self) > len(other) def __lt__(self, other): return len(self) < len(other) def __ge__(self, other): return len(self) >= len(other) def __le__(self, other): return len(self)
Теперь мы можем создать два Word (при помощи Word('foo') и Word('bar') ) и сравнить их по длине. Заметьте, что мы не определяли __eq__ и __ne__ , так как это приведёт к странному поведению (например, Word('foo') == Word('bar') будет расцениваться как истина). В этом нет смысла при тестировании на эквивалентность, основанную на длине, поэтому мы оставляем стандартную проверку на эквивалентность от str .
Сейчас, кажется, удачное время упомянуть, что вы не должны определять каждый из магических методов сравнения, чтобы полностью охватить все сравнения. Стандартная библиотека любезно предоставляет нам класс-декторатор в модуле functools , который и определит все сравнивающие методы, от вас достаточно определить только __eq__ и ещё один ( __gt__ , __lt__ и т.п.) Эта возможность доступна начиная с 2.7 версии Питона, но если это вас устраивает, вы сэкономите кучу времени и усилий. Для того, чтобы задействовать её, поместите @total_ordering над вашим определением класса.
Числовые магические методы
Точно так же, как вы можете определить, каким образом ваши объекты будут сравниваться операторами сравнения, вы можете определить их поведение для числовых операторов. Приготовтесь, друзья, их много. Для лучшей организации, я разбил числовые магические методы на 5 категорий: унарные операторы, обычные арифметические операторы, отражённые арифметические операторы (подробности позже), составные присваивания и преобразования типов.
Унарные операторы и функции
Унарные операторы и функции имеют только один операнд — отрицание, абсолютное значение, и так далее.
-
__pos__(self)
Определяет поведение для унарного плюса ( +some_object )
Обычные арифметические операторы
Теперь рассмотрим обычные бинарные операторы (и ещё пару функций): +, -, * и похожие. Они, по большей части, отлично сами себя описывают.
-
__add__(self, other)
Сложение.
Отражённые арифметические операторы
Помните как я сказал, что собираюсь остановиться на отражённой арифметике подробнее? Вы могли подумать, что это какая-то большая, страшная и непонятная концепция. На самом деле всё очень просто. Вот пример:
some_object + other
Это «обычное» сложение. Единственное, чем отличается эквивалентное отражённое выражение, это порядок слагаемых:
other + some_object
Таким образом, все эти магические методы делают то же самое, что и их обычные версии, за исключением выполнения операции с other в качестве первого операнда и self в качестве второго. В большинстве случаев, результат отражённой операции такой же, как её обычный эквивалент, поэтому при определении __radd__ вы можете ограничиться вызовом __add__ да и всё. Заметьте, что объект слева от оператора ( other в примере) не должен иметь обычной неотражённой версии этого метода. В нашем примере, some_object.__radd__ будет вызван только если в other не определён __add__ .
-
__radd__(self, other)
Отражённое сложение.
Составное присваивание
В Питоне широко представлены и магические методы для составного присваивания. Вы скорее всего уже знакомы с составным присваиванием, это комбинация «обычного» оператора и присваивания. Если всё ещё непонятно, вот пример:
x = 5 x += 1 # другими словами x = x + 1
Каждый из этих методов должен возвращать значение, которое будет присвоено переменной слева (например, для a += b , __iadd__ должен вернуть a + b , что будет присвоено a ). Вот список:
-
__iadd__(self, other)
Сложение с присваиванием.
Магические методы преобразования типов
Кроме того, в Питоне множество магических методов, предназначенных для определния поведения для встроенных функций преобразования типов, таких как float() . Вот они все:
-
__int__(self)
Преобразование типа в int.
Представление своих классов
Часто бывает полезно представление класса в виде строки. В Питоне существует несколько методов, которые вы можете определить для настройки поведения встроенных функций при представлении вашего класса.
-
__str__(self)
Определяет поведение функции str() , вызванной для экземпляра вашего класса.
Контроль доступа к атрибутам
Многие люди, пришедшие в Питон из других языков, жалуются на отсутствие настоящей инкапсуляции для классов (например, нет способа определить приватные атрибуты с публичными методами доступа). Это не совсем правда: просто многие вещи, связанные с инкапсуляцией, Питон реализует через «магию», а не явными модификаторами для методов и полей. Смотрите:
-
__getattr__(self, name)
Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё). Это может быть полезным для перехвата и перенаправления частых опечаток, предупреждения об использовании устаревших атрибутов (вы можете всё-равно вычислить и вернуть этот атрибут, если хотите), или хитро возвращать AttributeError , когда это вам нужно. Правда, этот метод вызывается только когда пытаются получить доступ к несуществующему атрибуту, поэтому это не очень хорошее решение для инкапсуляции.
def __setattr__(self, name, value): self.name = value # это рекурсия, так как всякий раз, когда любому атрибуту присваивается значение, # вызывается __setattr__(). # тоесть, на самом деле это равнозначно self.__setattr__('name', value). # Так как метод вызывает сам себя, рекурсия продолжится бесконечно, пока всё не упадёт def __setattr__(self, name, value): self.__dict__[name] = value # присваивание в словарь переменных класса # дальше определение произвольного поведения
Ещё раз, мощь магических методов в Питоне невероятна, а с большой силой приходит и большая ответственность. Важно знать, как правильно использовать магические методы, ничего не ломая.
Итак, что мы узнали об управлении доступом к атрибутам? Их не нужно использовать легкомысленно. На самом деле, они имеют склонность к чрезмерной мощи и нелогичности. Причина, по которой они всё-таки существуют, в удволетворении определённого желания: Питон склонен не запрещать плохие штуки полностью, а только усложнять их использование. Свобода первостепенна, поэтому вы на самом деле можете делать всё, что хотите. Вот пример использования методов контроля доступа (заметьте, что мы используем super , так как не все классы имеют атрибут __dict__ ):
class AccessCounter(object): '''Класс, содержащий атрибут value и реализующий счётчик доступа к нему. Счётчик увеличивается каждый раз, когда меняется value.''' def __init__(self, val): super(AccessCounter, self).__setattr__('counter', 0) super(AccessCounter, self).__setattr__('value', val) def __setattr__(self, name, value): if name == 'value': super(AccessCounter, self).__setattr__('counter', self.counter + 1) # Не будем делать здесь никаких условий. # Если вы хотите предотвратить изменение других атрибутов, # выбросьте исключение AttributeError(name) super(AccessCounter, self).__setattr__(name, value) def __delattr__(self, name): if name == 'value': super(AccessCounter, self).__setattr__('counter', self.counter + 1) super(AccessCounter, self).__delattr__(name)]
Создание произвольных последовательностей
В Питоне существует множество способов заставить ваши классы вести себя как встроенные последовательности (словари, кортежи, списки, строки и так далее). Это, безусловно, мои любимые магические методы, из-за до абсурда высокой степени контроля, которую они дают и той магии, от которой с экземплярами ваших классов вдруг начинает прекрасно работать целое множество глобальных функций. Но, до того как мы перейдём ко всяким хорошим вещам, мы должны знать о протоколах.
Протоколы
Теперь, когда речь зашла о создании собственных последовательностей в Питоне, пришло время поговорить о протоколах. Протоколы немного похожи на интерфейсы в других языках тем, что они предоставляют набор методов, которые вы должны реализовать. Однако, в Питоне протоколы абсолютно ни к чему не обязывают и не требуют обязательно реализовать какое-либо объявление. Наверное, они больше похожи на руководящие указания.
Почему мы заговорили о протоколах? Потому, что реализация произвольных контейнерных типов в Питоне влечёт за собой использование некоторых из них. Во-первых, протокол для определения неизменяемых контейнеров: чтобы создать неизменяемый контейнер, вы должны только определить __len__ и __getitem__ (продробнее о них дальше). Протокол изменяемого контейнера требует того же, что и неизменяемого контейнера, плюс __setitem__ и __delitem__ . И, наконец, если вы хотите, чтобы ваши объекты можно было перебирать итерацией, вы должны определить __iter__ , который возвращает итератор. Этот итератор должен соответствовать протоколу итератора, который требует методов __iter__ (возвращает самого себя) и next .
Магия контейнеров
Без дальнейшего промедления, вот магические методы, используемые контейнерами:
-
__len__(self)
Возвращает количество элементов в контейнере. Часть протоколов для изменяемого и неизменяемого контейнеров.
Пример
Для примера, давайте посмотрим на список, который реализует некоторые функциональные конструкции, которые вы могли встретить в других языках (Хаскеле, например).
class FunctionalList: '''Класс-обёртка над списком с добавлением некоторой функциональной магии: head, tail, init, last, drop, take.''' def __init__(self, values=None): if values is None: self.values = [] else: self.values = values def __len__(self): return len(self.values) def __getitem__(self, key): # если значение или тип ключа некорректны, list выбросит исключение return self.values[key] def __setitem__(self, key, value): self.values[key] = value def __delitem__(self, key): del self.values[key] def __iter__(self): return iter(self.values) def __reversed__(self): return FunctionalList(reversed(self.values)) def append(self, value): self.values.append(value) def head(self): # получить первый элемент return self.values[0] def tail(self): # получить все элементы после первого return self.values[1:] def init(self): # получить все элементы кроме последнего return self.values[:-1] def last(self): # получить последний элемент return self.values[-1] def drop(self, n): # все элементы кроме первых n return self.values[n:] def take(self, n): # первые n элементов return self.values[:n]
Теперь у вас есть полезный (относительно) пример реализации своей собственной последовательности. Существуют, конечно, и куда более практичные реализации произвольных последовательностей, но большое их число уже реализовано в стандартной библиотеке (с батарейками в комплекте, да?), такие как Counter , OrderedDict , NamedTuple .
Отражение
Вы можете контролировать и отражение, использующее встроенные функции isinstance() и issubclass() , определив некоторые магические методы. Вот они:
-
__instancecheck__(self, instance)
Проверяет, является ли экземлпяр членом вашего класса ( isinstance(instance, class) , например.
Вызываемые объекты
Как вы наверное уже знаете, в Питоне функции являются объектами первого класса. Это означает, что они могут быть переданы в функции или методы так же, как любые другие объекты. Это невероятно мощная особенность.
Специальный магический метод позволяет экземплярам вашего класса вести себя так, как будто они функции, тоесть вы сможете «вызывать» их, передавать их в функции, которые принимают функции в качестве аргументов и так далее. Это другая удобная особенность, которая делает программирование на Питоне таким приятным.
-
__call__(self, [args. ])
Позволяет любому экземпляру вашего класса быть вызванным как-будто он функция. Главным образом это означает, что x() означает то же, что и x.__call__() . Заметьте, __call__ принимает произвольное число аргументов; то есть, вы можете определить __call__ так же как любую другую функцию, принимающую столько аргументов, сколько вам нужно.
class Entity: '''Класс, описывающий объект на плоскости. "Вызываемый", чтобы обновить позицию объекта.''' def __init__(self, size, x, y): self.x, self.y = x, y self.size = size def __call__(self, x, y): '''Изменить положение объекта.''' self.x, self.y = x, y # чик.
Менеджеры контекста
В Питоне 2.5 было представлено новое ключевое слово вместе с новым способом повторно использовать код, ключевое слово with . Концепция менеджеров контекста не являлась новой для Питона (она была реализована раньше как часть библиотеки), но в PEP 343 достигла статуса языковой конструкции. Вы могли уже видеть выражения с with :
with open('foo.txt') as bar: # выполнение каких-нибудь действий с bar
Менеджеры контекста позволяют выполнить какие-то действия для настройки или очистки, когда создание объекта обёрнуто в оператор with . Поведение менеджера контекста определяется двумя магическими методами:
-
__enter__(self)
Определяет, что должен сделать менеджер контекста в начале блока, созданного оператором with . Заметьте, что возвращаемое __enter__ значение и есть то значение, с которым производится работа внутри with .
class Closer: '''Менеджер контекста для автоматического закрытия объекта вызовом метода close в with-выражении.''' def __init__(self, obj): self.obj = obj def __enter__(self): return self.obj # привязка к активному объекту with-блока def __exit__(self, exception_type, exception_val, trace): try: self.obj.close() except AttributeError: # у объекта нет метода close print 'Not closable.' return True # исключение перехвачено
Пример использования Closer с FTP-соединением (сокет, имеющий метод close):
>>> from magicmethods import Closer >>> from ftplib import FTP >>> with Closer(FTP('ftp.somesite.com')) as conn: . conn.dir() . # output omitted for brevity >>> conn.dir() # long AttributeError message, can't use a connection that's closed >>> with Closer(int(5)) as i: . i += 1 . Not closable. >>> i 6
Видите, как наша обёртка изящно управляется и с правильными и с неподходящими объектами. В этом сила менеджеров контекста и магических методов. Заметьте, что стандартная библиотека Питона включает модуль contextlib, который включает в себя contextlib.closing() — менеджер контекста, который делает приблизительно то же (без какой-либо обработки случая, когда объект не имеет метода close() ).
Абстрактные базовые классы
Построение дескрипторов
Дескрипторы это такие классы, с помощью которых можно добавить свою логику к событиям доступа (получение, изменение, удаление) к атрибутам других объектов. Дескрипторы не подразумевается использовать сами по себе; скорее, предполагается, что ими будут владеть какие-нибудь связанные с ними классы. Дескрипторы могут быть полезны для построения объектно-ориентированных баз данных или классов, чьи атрибуты зависят друг от друга. В частности, дескрипторы полезны при представлении атрибутов в нескольких системах исчисления или каких-либо вычисляемых атрибутов (как расстояние от начальной точки до представленной атрибутом точки на сетке).
Чтобы класс стал дескриптором, он должен реализовать по крайней мере один метод из __get__ , __set__ или __delete__ . Давайте рассмотрим эти магические методы:
-
__get__(self, instance, instance_class)
Определяет поведение при возвращении значения из дескриптора. instance это объект, для чьего атрибута-дескриптора вызывается метод. owner это тип (класс) объекта.
class Meter(object): '''Дескриптор для метра.''' def __init__(self, value=0.0): self.value = float(value) def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = float(value) class Foot(object): '''Дескриптор для фута.''' def __get__(self, instance, owner): return instance.meter * 3.2808 def __set__(self, instance, value): instance.meter = float(value) / 3.2808 class Distance(object): '''Класс, описывающий расстояние, содержит два дескриптора для футов и метров.''' meter = Meter() foot = Foot()
Копирование
В Питоне оператор присваивания не копирует объекты, а только добавляет ещё одну ссылку. Но для коллекций, содержащих изменяемые элементы, иногда необходимо полноценное копирование, чтобы можно было менять элементы одной последовательности, не затрагивая другую. Здесь в игру и вступает copy . К счастью, модули в Питоне не обладают разумом, поэтому мы можем не беспокоиться что они вдруг начнут бесконтрольно копировать сами себя и вскоре линуксовые роботы заполонят всю планету, но мы должны сказать Питону как правильно копировать.
-
__copy__(self)
Определяет поведение copy.copy() для экземпляра вашего класса. copy.copy() возвращает поверхностную копию вашего объекта — это означает, что хоть сам объект и создан заново, все его данные ссылаются на данные оригинального объекта. И при изменении данных нового объекта, изменения будут происходить и в оригинальном.
Использование модуля pickle на своих объектах
Pickle это модуль для сериализации структур данных Питона и он может быть невероятно полезен, когда вам нужно сохранить состояние какого-либо объекта и восстановить его позже (обычно, в целях кэширования). Кроме того, это ещё и отличный источник переживаний и путаницы.
Сериализация настолько важна, что кроме своего модуля ( pickle ) имеет и свой собственный протокол и свои магические методы. Но для начала о том, как сериализовать с помощью pickle уже существующие типы данных (спокойно пропускайте, если вы уже знаете).
Вкратце про сериализацию
Давайте погрузимся в сериализацию. Допустим, у вас есть словарь, который вы хотите сохранить и восстановить позже. Вы должны записать его содержимое в файл, тщательно убедившись, что пишете с правильным синтаксисом, потом восстановить его, или выполнив exec() , или прочитав файл. Но это в лучшем случае рискованно: если вы храните важные данные в тексте, он может быть повреждён или изменён множеством способов, с целью обрушить вашу программу или, вообще, запустить какой-нибудь опасный код на вашем компьютере. Лучше использовать pickle:
import pickle data = jar = open('data.pkl', 'wb') pickle.dump(data, jar) # записать сериализованные данные в jar jar.close()
И вот, спустя несколько часов, нам снова нужен наш словарь:
import pickle pkl_file = open('data.pkl', 'rb') # открываем data = pickle.load(pkl_file) # сохраняем в переменную print data pkl_file.close()
Что произошло? Точно то, что и ожидалось. data как-будто всегда тут и была.
Теперь, немного об осторожности: pickle не идеален. Его файлы легко испортить случайно или преднамеренно. Pickle, может быть, безопаснее чем текстовые файлы, но он всё ещё может использоваться для запуска вредоносного кода. Кроме того, он несовместим между разными версиями Питона, поэтому если вы будете распространять свои объекты с помощью pickle, не ожидайте что все люди смогут их использовать. Тем не менее, модуль может быть мощным инструментом для кэширования и других распространённых задач с сериализацией.
Сериализация собственных объектов.
Модуль pickle не только для встроенных типов. Он может использоваться с каждым классом, реализующим его протокол. Этот протокол содержит четыре необязательных метода, позволяющих настроить то, как pickle будет с ними обращаться (есть некоторые различия для расширений на C, но это за рамками нашего руководства):
-
__getinitargs__(self)
Если вы хотите, чтобы после десериализации вашего класса был вызыван __init__ , вы можете определить __getinitargs__ , который должен вернуть кортеж аргументов, который будет отправлен в __init__ . Заметьте, что этот метод работает только с классами старого стиля.
Пример
Для примера опишем грифельную доску ( Slate ), которая запоминает что и когда было на ней записано. Впрочем, конкретно эта доска становится чистой каждый раз, когда она сериализуется: текущее значение не сохраняется.
import time class Slate: '''Класс, хранящий строку и лог изменений. И забывающий своё значение после сериализации.''' def __init__(self, value): self.value = value self.last_change = time.asctime() self.history = <> def change(self, new_value): # Изменить значение. Зафиксировать последнее значение в истории. self.history[self.last_change] = self.value self.value = new_value self.last_change = time.asctime() def print_changes(self): print 'Changelog for Slate object:' for k, v in self.history.items(): print '%s\t %s' % (k, v) def __getstate__(self): # Намеренно не возвращаем self.value or self.last_change. # Мы хотим "чистую доску" после десериализации. return self.history def __setstate__(self, state): self.history = state self.value, self.last_change = None, None
Заключение
Цель этого руководства донести что-нибудь до каждого, кто его читает, независимо от его опыта в Питоне или объектно-ориентированном программировании. Если вы новичок в Питоне, вы получили ценные знания об основах написания функциональных, элегантных и простых для использования классов. Если вы программист среднего уровня, то вы, возможно, нашли несколько новых приятных идей и стратегий, несколько хороших способов уменьшить количество кода, написанного вами и вашими клиентами. Если же вы Питонист-эксперт, то вы обновили некоторые свои, возможно, подзабытые знания, а может и нашли парочку новых трюков. Независимо от вашего уровня, я надеюсь, что это путешествие через специальные питоновские методы было поистине магическим (не смог удержаться).
Дополнение 1: Как вызывать магические методы
Некоторые из магических методов напрямую связаны со встроенными функциями; в этом случае совершенно очевидно как их вызывать. Однако, так бывает не всегда. Это дополнение посвящено тому, чтобы раскрыть неочевидный синтаксис, приводящий к вызову магических методов.
| Магический метод | Когда он вызывается (пример) | Объяснение |
|---|---|---|
| __new__(cls [. ]) | instance = MyClass(arg1, arg2) | __new__ вызывается при создании экземпляра |
| __init__(self [. ]) | instance = MyClass(arg1, arg2) | __init__ вызывается при создании экземпляра |
| __cmp__(self, other) | self == other , self > other , etc. | Вызывается для любого сравнения |
| __pos__(self) | +self | Унарный знак плюса |
| __neg__(self) | -self | Унарный знак минуса |
| __invert__(self) | ~self | Побитовая инверсия |
| __index__(self) | x[self] | Преобразование, когда объект используется как индекс |
| __nonzero__(self) | bool(self) , if self: | Булевое значение объекта |
| __getattr__(self, name) | self.name # name не определено | Пытаются получить несуществующий атрибут |
| __setattr__(self, name, val) | self.name = val | Присвоение любому атрибуту |
| __delattr__(self, name) | del self.name | Удаление атрибута |
| __getattribute__(self, name) | self.name | Получить любой атрибут |
| __getitem__(self, key) | self[key] | Получение элемента через индекс |
| __setitem__(self, key, val) | self[key] = val | Присвоение элементу через индекс |
| __delitem__(self, key) | del self[key] | Удаление элемента через индекс |
| __iter__(self) | for x in self | Итерация |
| __contains__(self, value) | value in self , value not in self | Проверка принадлежности с помощью in |
| __call__(self [. ]) | self(args) | «Вызов» экземпляра |
| __enter__(self) | with self as x: | with оператор менеджеров контекста |
| __exit__(self, exc, val, trace) | with self as x: | with оператор менеджеров контекста |
| __getstate__(self) | pickle.dump(pkl_file, self) | Сериализация |
| __setstate__(self) | data = pickle.load(pkl_file) | Сериализация |
Надеюсь, эта таблица избавит вас от любых вопросов о том, что за синтаксис вызова магических методов.
Дополнение 2: Изменения в Питоне 3
Опишем несколько главных случаев, когда Питон 3 отличается от 2.x в терминах его объектной модели:
- Так как в Питоне 3 различий между строкой и юникодом больше нет, __unicode__ исчез, а появился __bytes__ (который ведёт себя так же как __str__ и __unicode__ в 2.7) для новых встроенных функций построения байтовых массивов.
- Так как деление в Питоне 3 теперь по-умолчанию «правильное деление», __div__ больше нет.
- __coerce__ больше нет, из-за избыточности и странного поведения.
- __cmp__ больше нет, из-за избыточности.
- __nonzero__ было переименовано в __bool__ .
- next у итераторов был переименован в __next__ .
Объектно-ориентированное программирование. Специальные методы.
Декоратор @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 в структуре или классе swift
Конкретно здесь наверно и не нужен, но, например, можно делать для одной структуры или класса разные инициализаторы, которые могут принимать разные типы данных, из которых инициализатор каким-то образом извлекает то, что ему нужно. То есть вы можете туда передать, например, другую структуру из которой извлекутся нужные для работы данные. И самое главное, эта логика будет скрыта.
Вот например ваш измененный пример: простейшее и тоже в каком-то смысле бессмысленное в данном случае дополнение 🙂
import Foundation struct Book < var title: String init (title: String) < self.title = title >init (number: Int) < self.title = "Number of book: " + String(number) >> var book = Book(title: "Doom") print(book.title) book = Book(number: 777) print(book.title)
Естественно количество переменных в инициализаторе может меняться, как и их тип. Это одно из объяснений необходимости инициализаторов. Может еще напишут про другие.
В случае с Классом - инициализатор требуется, если не были установлены значения свойств по умолчанию.
class SomeClass
Здесь компилятор скажет вам что класс SomeClass не имеет инициализатора и не знает какое значение установить свойству value. Поэтому необходимо либо указать значение по умолчанию, либо установить значение свойству в инициализаторе
class SomeClass < var value: Int init(_ value: Int) < self.value = value >>
Инициализация и протоколы — 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 студентов
Наши выпускники работают в компаниях:
Как создать инициализатор класса someclass

Комментарии
Популярные По порядку
Не удалось загрузить комментарии.
ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ
13 лучших книг по Python для начинающих и продолжающих
Представляем вам подборку лучших книг по Python для начинающих программистов, опытных питонистов и детей, с реальными рецензиями.
3 самых важных сферы применения Python: возможности языка
Существует множество областей применения Python, но в некоторых он особенно хорош. Разбираемся, что же можно делать на этом ЯП.
Программирование на Python: от новичка до профессионала
Пошаговая инструкция для всех, кто хочет изучить программирование на Python (или программирование вообще), но не знает, куда сделать первый шаг.