Конструктор класса – метод __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
Несколько конструкторов с разным количеством аргументов
В данном случае можно создавать экземпляры класса только со вторым констуктором, в то время как запись x = a(42) вызовет ошибку.
Отслеживать
51.4k 86 86 золотых знаков 267 267 серебряных знаков 505 505 бронзовых знаков
задан 13 фев 2013 в 19:16
119 1 1 золотой знак 3 3 серебряных знака 10 10 бронзовых знаков
4 ответа 4
Сортировка: Сброс на вариант по умолчанию
Если быть кратким то НЕТ.
Более детально посмотрите обсуждение вот ЗДЕСЬ. Там и варианты решений есть такие как использование необязательных или ключевых аргументов.
Отслеживать
3 4 4 бронзовых знака
ответ дан 13 фев 2013 в 19:21
3,724 14 14 серебряных знаков 16 16 бронзовых знаков
В Python’е перегрузок функций (а конструктор, по факту — это функция) нет в принципе. В вашем случае, вызывается всегда второй конструктор, ибо инструкция def означает «создать объект функции и присвоить этот объект написанному имени», то есть, сначала вы присваиваете имени __init__ одну функцию (с параметром b ), а затем — тому же имени — другую функцию, уже без параметра b .
Поведение, которого вы добиваетесь, можно получить, используя параметры по умолчанию, например:
def __init__(self, b=None): # Здесь лучше придумать что-то отличное от проверки на None # больше соответствующее вашей задаче. Возможно, вам просто подойдет # какое-то значение по умолчанию. if b is None: pass else: pass # . a = a() # b будет равно None по умолчанию b = a(42) # b будет равно 42
Вероятно, вы неправильно используете метод __init__ в Python
Почему вам следует делать ваши конструкторы простыми

Python — объектно-ориентированный язык. Способ создания нового объекта обычно определяется в специальном методе __init__ , реализованном в классе. Простой класс, хранящий две переменные экземпляра, можно реализовать следующим образом:
class MyClass: def __init__(self, attr1, attr2): self.attr1 = attr1 self.attr2 = attr2 def get_variables(self): return self.attr1, self.attr2 my_object = MyClass("value1", "value2") my_object.get_variables() # -> ("value1", "value2")
Создание объекта следует синтаксису () . В нашем случае метод __init__ принимает два аргумента, которые хранятся как переменные экземпляра. После создания объекта можно вызывать методы, использующие эти данные.
Однако большинство объектов строятся гораздо сложнее. Часто данные, которые объект должен хранить, недоступны, и их необходимо получить из других входных данных. Зачастую мы хотим иметь возможность создавать один и тот же объект из разных типов аргументов.
Ошибка, которую я вижу во многих кодовых базах Python, заключается в том, что вся эта логика встроена в метод __init__ . Тот факт, что всё происходит в __init__ , можно изменить каким-нибудь вспомогательным методом _initialize , но результат всегда один: логика создания объектов Python превращается в непонятное чудище.
Давайте посмотрим на пример. У нас есть объект, представляющий некоторый набор конфигураций, которые мы обычно загружаем из файла. Я видел, как такой класс был реализован следующим образом:
class Configuration: def __init__(self, filepath): self.filepath = filepath self._initialize() def _initialize(self): self._parse_config_file() self._precompute_stuff() def _parse_config_file(self): # распарсить файл self.filepath, и сохранить # данные в нескольких переменных self. . def _precompute_stuff(self): # использовать переменные, определенные в # self._parse_config_file, для вычисления и установки # новых переменных экземпляра .
Но что в этом плохого? Две вещи:
- Очень сложно судить о состоянии объекта при его создании. Какие переменные экземпляра определены и каковы их значения? Чтобы это выяснить, мы должны пройти всю иерархию функций инициализации и принять во внимание любые присвоения self. . В этом фиктивном примере это всё ещё возможно, но я видел примеры, где вызываемый в __init__ код состоит из более чем 1000 строк и включает методы, вызываемые из суперкласса.
- Логика создания теперь жёстко запрограммирована. Нет другого способа создать объект Configuration , кроме как указать путь к файлу, поскольку для создания объекта всегда необходимо пройти через метод __init__ . На данный момент мы всегда можем создать Configuration из файла, но кто сказал, что так будет и в будущем? Кроме того, хотя реальному приложению может потребоваться только один способ создания экземпляра, для тестирования может быть удобно создать объект-пустышку, не полагаясь на дополнительный файл.
Эти проблемы обычно проявляются на поздней стадии разработки. Тогда многие разработчики Python пытаются решить проблему, еще больше ухудшая ситуацию. Например:
- Разрешить входной переменной иметь несколько типов, затем проверить, к какому типу относятся входные данные экземпляра и перейти к другой ветке инициализации в зависимости от результата. В нашем примере мы могли бы изменить входную переменную filepath на config и позволить ей быть строкой или словарём, который мы будем интерпретировать соответственно как путь к файлу или уже проанализированные данные.
- Добавление аргументов, которые переопределяют друг друга. Например, мы могли бы принять оба аргумента config и filepath и игнорировать filepath , если указан config .
- Добавление аргументов, которые могут быть логическими значениями или перечислением, для выбора ветвей в логике инициализации. Например, если у нас есть несколько версий одного и того же файла конфигурации, мы можем просто добавить аргумент version в __init__ .
- Добавление *args или **kwargs в __init__ , потому что тогда сигнатуру __init__ больше не нужно будет менять, но логика реализации может меняться при необходимости.
Почему всё это — плохие решения? По сути, потому что это костыли, решающие одну проблему, но делающие другую ещё хуже. Если у вас возникли проблемы с логикой инициализации и вы используете одну из вышеперечисленных стратегий, подумайте о том, чтобы сделать шаг назад и использовать альтернативный подход.
Чтобы решить проблему, я стараюсь следовать подходу, который заключается в том, чтобы рассматривать почти каждый класс как dataclass или NamedTuple (без обязательного использования этих примитивов напрямую). Это означает, что мы должны думать об объекте не иначе как о наборе связанных данных. Класс определяет имена полей данных и их типы и, при необходимости, реализует методы для работы с этими данными. Метод __init__ не должен делать ничего, кроме присвоения этих данных; его аргументы должны непосредственно соответствовать переменным экземпляра. Многие другие языки имеют встроенную конструкцию для этой концепции: struct .
Почему это предпочтительнее любого другого объекта Python?
- Это заставляет вас думать о данных, которые действительно необходимы объекту для функционирования. Это защитит от установки множества бесполезных переменных экземпляра в __init__ «на всякий случай» или, что еще хуже, от установки разных переменных экземпляра в разных ветках.
- Это ставит состояние на первый план для читающих код и отделяет его от любой логики манипулирования данными. И сразу позволяет понять, какие атрибуты определены для объекта. Все сгруппировано вместе. В инициализации объекта нет никакой магии.
- Это значительно упрощает создание объектов различными способами, например путем определения фабричных методов или конструкторов. Также это облегчит тестирование.
Для иллюстрации давайте посмотрим на альтернативную реализацию нашего класса Configuration :
class Configuration: def __init__(self, attr1, attr2): self.attr1 = attr1 self.attr2 = attr2 @classmethod def from_file(cls, filepath): parsed_data = cls._parse_config_file(filepath) computed_data = cls._precompute_stuff(parsed_data) return cls( attr1=parsed_data, attr2=computed_data, ) @classmethod def _parse_config_file(cls, filepath): # разбираем файл по указанному пути и возвращаем данные . @classmethod def _precompute_stuff(cls, data): # используем данные, полученные из конфигурационного файла, # для расчета новых данных .
Здесь метод __init__ минимален настолько, насколько это возможно. Сразу понятно, что Configuration должен хранить два атрибута. То, как мы получаем данные для этих двух атрибутов, не является заботой __init__ .
Вместо передачи пути к файлу в конструктор теперь у нас есть фабричный метод from_file , реализованный как classmethod . Мы также преобразовали наши методы синтаксического анализа и вычислений в classmethod , которые теперь принимают входные данные и возвращают результаты. Данные, возвращаемые этими методами, передаются конструктору, и возвращается результирующий объект.
Преимущества этого подхода:
- Легче понять и рассуждать о состоянии. Сразу становится ясно, какие атрибуты экземпляра определяются для объекта после создания экземпляра.
- Легче тестировать. Наши функции инициализации — это чистые функции, которые можно вызывать изолированно и которые не полагаются на уже существующее состояние объекта.
- Легче расширять. Мы можем легко реализовать дополнительные фабричные методы для создания объекта Configuration альтернативными способами, например из словаря.
- Легче быть последовательным. Следовать этому подходу в большинстве ваших классов легче, чем постоянно заново изобретать сложную логику пользовательской инициализации.
Вы также можете рассмотреть возможность полного отделения кода создания от самого класса, например, переместив логику в функцию или класс Factory .
Builders (строители) — это альтернатива фабрикам, когда вам нужен высокий уровень гибкости при создании ваших объектов. Идея состоит в том, чтобы использовать вспомогательный объект « builder » с сохранением состояния, который вы модифицируете, вызывая его методы. Затем, когда желаемое состояние создано, вызов метода типа build создаёт интересующий вас объект. Когда вы обнаружите, что вам нужно много аргументов или много логики в фабричном методе, вы можете рассмотреть шаблон builder . Обратной стороной этого шаблона является то, что его сложнее тестировать.
К сожалению, фабричные методы и строители довольно редки в кодовых базах Python, по крайней мере, в пользовательских API. Многие программисты ожидают, что объекты всегда будут создаваться путем прямого вызова конструктора, и это отражено в API большинства популярных библиотек. Обычно вы хотите предоставить пользователям API, с которым они знакомы. В этом случае вы все равно можете использовать некоторые стратегии, описанные выше, но реализовать собственный метод __new__ , чтобы предоставить знакомый API инициализации.
Ожидания относительно того, как «должен выглядеть» Python, отчасти объясняют, почему методы __init__ имеют тенденцию стремительно усложняться. Но есть и другие причины, связанные с гибкостью Python, из-за которых очень легко сделать неправильный выбор:
- Динамическая типизация: переменные могут изменить тип в любое время.
- Нет инкапсуляции: все атрибуты общедоступны.
- Нет неизменяемости: большинство атрибутов изменяемы.
Это означает, что по умолчанию новым переменным экземпляра может быть присвоено любое значение любого объекта в любое время любым другим объектом. Это здорово, когда нужно найти быстрое решение. В Python никогда не возникает «необходимости» думать о фабриках или конструкторах; вы просто собираете его на лету! Однако это ужасно для создания поддерживаемого кода. Очень сложно отлаживать и анализировать состояние программы, если из кода не ясно, где создаётся или изменяется состояние.
Существует ряд стратегий улучшения, и все они предполагают наложение ограничений на Python.
Во-первых, чтобы решить некоторые проблемы с динамической типизацией, вам следует внедрить статическую проверку типов с помощью mypy (https://www.mypy-lang.org/) и использовать строгие (strict) настройки. Mypy достаточно хорошо понимает состояние объекта, т. е. какие переменные определены в объекте и какие типы им присвоены в методе __init__ . Mypy можно настроить так, чтобы запретить все другие новые присвоения переменных. Это должно защитить вас от некоторых грубых ошибок во время выполнения программы, таких как вызов методов, которые используют несуществующие атрибуты или атрибуты, имеющие значение None . Mypy также не позволяет изменять тип переменной, поэтому вы не сможете быть небрежными с Optional типами, т.е. вы не сможете просто инициализировать переменные как None и позже присвоить им что-то ещё. В конечном счете, статический анализ типов поможет вам выявить проблемы в дизайне: если вы не можете соответствовать требованиям mypy , вам, вероятно, следует переосмыслить свою архитектуру.
Во-вторых, чтобы улучшить инкапсуляцию, сделайте все переменные экземпляра закрытыми, что означает, что доступ к ним можно получить только методами самого объекта. На самом деле это невозможно реализовать в Python, но по соглашению любой атрибут, начинающийся с символа « _ », считается закрытым. Поэтому, если вы обнаружите, что используете метод или переменную, начинающуюся с « _ » вне методов объекта, вам следует пересмотреть свой дизайн. Языковые серверы и IDE будут соблюдать это соглашение и не будут отображать эти методы или переменные в меню автодополнения (если только вы явно не введёте « _ »). Вы можете сделать переменные экземпляра почти полностью приватными, поставив перед ними префикс двойного подчёркивания.
В-третьих, по возможности выбирайте неизменяемость. Статическое состояние гораздо легче понять, чем изменяемое. Обеспечение неизменяемости может быть достигнуто несколькими способами. Если вы используете частные переменные экземпляра и предоставляете их только с помощью метода получения, это способствует неизменяемости. Вы также можете попытаться использовать неизменяемые структуры данных, такие как кортежи, вместо списков. Если вы не можете выбрать между dataclass или NamedTuple , следует отдать предпочтение NamedTuple , поскольку его поля неизменяемы.
Применяя эти дополнительные предложения к нашему предыдущему примеру, мы приходим к следующему:
from __future__ import annotations class Configuration: def __init__(self, attr1: int, attr2: int) -> None: self._attr1 = attr1 self._attr2 = attr2 @property def attr1(self) -> int: return self._attr1 @property def attr2(self) -> int: return self._attr2 @classmethod def from_file(cls, filepath: str) -> Configuration: parsed_data = cls._parse_config_file(filepath) computed_data = cls._precompute_stuff(parsed_data) return cls( attr1=parsed_data, attr2=computed_data, ) @classmethod def _parse_config_file(cls, filepath: str) -> int: # разбираем файл по указанному пути и возвращаем данные . @classmethod def _precompute_stuff(cls, data: int) -> int: # используем данные, полученные из конфигурационного файла, # для расчета новых данных .
Заключение
Старайтесь, чтобы методы __init__ ваших классов были простыми, и думайте о классах как о структурах. Переместите логику построения объектов в фабричные методы или «builders». Это облегчит чтение вашего кода, его тестирование и расширение в будущем. Кроме того, используйте статический анализ типов, инкапсуляцию и неизменяемость для принятия архитектурных решений и написания более надёжного кода Python.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
Язык Python
Язык python полноценно поддерживает объектно-ориентированную разработку. Минимальный класс в python можно создать следующим образом:
class NewGreatType: pass obj = NewGreatType() type(obj) #
Любой объект в python является объектом какого-либо класса:
type(1) # type(int) # type(NewGreatType) # type(type) #
Эти примеры показывают, что типы данных сами являются объектами класса type . Встроенная функция dir позволяет получить все атрибуты (поля и методы) объекта. Выведем для примера атрибуты объекта False класса bool :
dir(False) # ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', # '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', # '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', # '__getattribute__', '__getnewargs__', '__gt__', '__hash__', # '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', # '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', # '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', # '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', # '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', # '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', # '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', # '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', # 'as_integer_ratio', 'bit_length', 'conjugate', 'denominator', # 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
Довольно много для такого простого объекта. Попробуем вызвать метод __or__ :
False.__or__(False) # False False.__or__(True) # True
Вызов этого метода эквивалентен использованию оператора or . Мы обнаружили способ перегрузки операторов в python. Она выполняется с помощью определения «магических» методов, некоторые из которых мы рассмотрим ниже.
Поля и методы
Вспомним класс LorentzVector , с которым мы работали в разделе про классы в C++ и начнем писать его аналог на python:
class LorentzVector: """ Релятивистский вектор """ def __init__(self, t, x): """ x: может быть int, float или list """ self.t = t self.x = x def r2(self): """ Квадрат модуля пространственной компоненты """ if isinstance(self.x, (int, float)): return self.x**2 return sum(map(lambda a: a**2, self.x)) def inv(self): """ Релятивистский инвариант """ return self.t**2 - self.r2()
Метод __init__ является конструктором. Объект self ссылается на сам объект класса (аналог this в C++) и является обязательным первым аргументом всех нестатических методов, включая конструктор. В конструкторе мы определили два поля класса: self.x и self.y . Также мы определили два метода: r2 возвращает квадрат пространственной компоненты вектора, и inv возвращает релятивистский инвариант, соответствующий вектору.
Все поля и методы класса в python являются публичными. Разделение интерфейса и деталей реализации происходит на уровне соглашения об именах полей и методов: если атрибут не является частью интерфейса, то его имя должно начинаться с двух подчеркиваний, например: __internal_variable .
Проверим работу класса LorentzVector :
lv1 = LorentzVector(1, 0.5) lv1.t # 1 lv1.x # 0.5 lv1.r2() # 0.25 lv1.inv() # 0.75
lv2 = LorentzVector(1, [0.3, 0.4, 0.0]) lv2.t # 1 lv2.x # [0.3, 0.4, 0.0] lv2.r2() # 0.25 lv2.inv() # 0.75
Атрибуты могут определяться не только в конструкторе, но и в любом другом методе класса. Более того, атрибуты можно определять прямо в пользовательском коде:
lv = LorentzVector(1, 0.5) hasattr(lv, 'mass') # False lv.mass = 0.3 hasattr(lv, 'mass') # True
Атрибуты класса (статические атрибуты) определяются сразу после названия класса:
class LorentzVector: """ Релятивистский вектор """ speed_of_light = 2.99792458e10 # см / с # . LorentzVector.speed_of_light # 29979245800.0
Для задания статического метода необходимо использовать декоратор staticmethod :
class LorentzVector: # . @staticmethod def boost_vector(lv): """ Возвращает boost-вектор для данного вектора """ if isinstance(lv.x, (int, float)): return lv.x / lv.t return list(map(lambda x: x / lv.t, lv.x))
Как и следовало ожидать, статический метод не имеет аргумента self . Декораторы — это инструмент python, позволяющий менять поведение функций. Технически — это функция, которая принимает на вход некоторую функцию, и возвращает новую функцию с тем же набором аргументов.
С помощью декоратора property можно делать вызов методов, не имеющих аргументов, похожим на обращение к полю класса:
class LorentzVector: # . @property def r2(self): """ Квадрат модуля пространственной компоненты """ if isinstance(self.x, (int, float)): return self.x**2 return sum(map(lambda a: a**2, self.x)) @property def inv(self): """ Релятивистский инвариант """ return self.t**2 - self.r2()
lv1 = LorentzVector(1, 0.5) lv1.t # 1 lv1.x # 0.5 lv1.r2 # 0.25 lv1.inv # 0.75
Магические методы
Интеграция пользовательских классов в среду языка выполняется посредством определения специальных методов. Начнём с рассмотрения примера перегрузки арифметического оператора:
class LorentzVector: # . def __add__(self, rhs): """ Сложение двух векторов """ if isinstance(self.x, (int, float)): x_new = self.x + rhs.x else: x_new = list(map(lambda a: sum(a), zip(self.x, rhs.x))) return LorentzVector(self.t + rhs.t, x_new)
Аналогично выполняется перегрузка операторов вычитания ( __sub__ ), умножения ( __mul__ ), деления ( __div__ ), круглых скобок ( __call__ ), квадратных скобок ( __getitem__ ) и т.д.
Методы __str__ и __repr__ отвечают за текстовое представление объекта. Метод __str__ вызывается, когда объект передается в функцию print или в форматированную строку, и служит для «неформального» представления объекта. Метод __repr__ должен возвращать строку, которая содержит всю информацию о состоянии объекта и по которой объект может быть восстановлен. Если определен только метод __repr__ , то он будет вызываться в функции print вместо метода __str__ .
Если передать объект LorentzVector в функцию print , не определяя специальных методов, то мы получим что-то подобное:
lv = LorentzVector(1, 0.5) print(lv) # __main__.LorentzVector object at 0x7f50a36dfeb0>
Функция print вывела тип объекта и адрес, по которому он расположен в памяти.
Определим метод __repr__ :
class LorentzVector: # . def __repr__(relf): """ Текстовое представление вектора """ return f'LorentzVector [self.t>, self.x>]'
lv = LorentzVector(1, 0.5) print(lv) # LorentzVector [1, 0.5]
Мы рассмотрели лишь некоторые из доступных специальных методов. Рекомендуем ознакомиться с полным списком в документации.
Наследование
Язык python позволяет выполнять наследование классов. Класс-потомок имеет доступ ко всем полям и методам класса-предка. Все классы в python являются наследниками класса object . Об класса object наш класс LorentzVector наследует большинство своих атрибутов:
lv = LorentzVector(1, 0.5) isinstance(lv, object) # True dir(lv) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', # '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', # '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', # '__module__', '__ne__', '__new__', '__reduce__', # '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', # '__str__', '__subclasshook__', '__weakref__', 'inv', 'mass', # 'r2', 'sum', 't', 'x']
Используем механизм наследования, чтобы реализовать тип релятивистского вектора иначе. Новый тип будет наследником именованного кортежа NamedTuple :
from typing import NamedTuple class FourVector(NamedTuple): # """ Релятивистский вектор """ t: float # такой синтаксис используется для задания r: list # полей кортежа и аннотации их типов @property def r2(self): """ Квадрат модуля пространственной компоненты """ return sum(map(lambda a: a**2, self.r)) @property def inv(self): """ Релятивистский инвариант """ return self.t**2 - self.r2() def __repr__(relf): """ Текстовое представление вектора """ return f'FourVector [self.t>, self.r>]'
Новая реализация яснее показывает структуру нашего типа данных. Для создания объекта FourVector необходимо задать значения полям t и r :
fv1 = FourVector(1, [0.3, 0.4, 0.0]) fv2 = FourVector(t=1, r=[0.3, 0.4, 0.0]) fv3 = FourVector(t=1, r=[0.5]) fv4 = FourVector(t=1, r=0.5) fv3.r2 # 0.25 fv4.r2 # TypeError: 'float' object is not iterable
При создании объекта fv4 мы нарушили соглашение и передали в поле r объект float вместо list . Это привело к ошибке при вызове метода r2 . При определении класса FourVector мы использовали аннотацию типов ( t: float ).
Больше информации о наследовании в python можно найти в документации.
Полиморфизм в python
Реализация полиморфизма в python сильно отличается от его реализации в C++. Полиморфизм в C++ реализуется с помощью инструментов наследования и шаблонов. Динамическая типизация python позволяет использовать гораздо более гибкие инструменты полиморфизма. Переменные, аргументы функций и атрибуты классов в python могут в разных контекстах иметь разные типы и даже менять тип со временем. Таким образом, все объекты в python изначально полиморфны.
В такой ситуации возникает вопрос о том как описывать ограничения на допустимые типы объектов. Здесь на помощь приходит принцип утиной типизации (duck typing), дословно состоящий в том, что «если что-то выглядит как утка и крякает как утка, значит это утка». Иными словами, если объект предоставляет необходимый интерфейс, то мы можем с ним работать вне зависимости от его типа. Для поддержки этого подхода в python реализованы инструменты для проверки свойств объектов:
# проверяет является ли obj объектом типа int или float isinstance(obj, (int, float)) # проверяет имеет ли obj атрибут norm hasattr(obj, 'norm') # является ли класс FourVector подклассом NamedTuple issubclass(FourVector, NamedTuple)
Столь гибкая типизация приводит к необходимости качественной документации кода. Хорошим стилем является описание всех контрактов функции или метода в его строке комментария. Значительно улучшает читаемость кода и аннотация типов.
Резюме
В этом разделе мы выполнили краткий обзор инструментов python, реализующих парадигму объектно-ориентированного программирования. Обсудили создание классов; определение полей и методов; статических полей и методов; определение специальных методов, позволяющих интегрировать тип данных в среду языка; кратко рассмотрели наследование классов и принципы реализации полиморфизма в python.
Концепция ООП в python не является основной, как в C++, однако средства ООП составляют важную часть языка, и их понимание необходимо для грамотной разработки на python, поскольку все типы объектов в python являются классами.
Источники
- docs.python.org/3/reference/datamodel
- docs.python.org/3/tutorial/classes
- docs.python.org/3/library/typing
- Python Inheritance
- Понимаем декораторы в Python’e, шаг за шагом (habr)
- Введение в аннотации типов Python (habr)
- Введение
- Настройка рабочей среды
- Язык C++
- Язык Python
- Основы синтаксиса языка python
- Строки
- Стандартные модули python, часть I
- ООП в python
- Стандартные модули II
- Итераторы и генераторы
- Вычисления с библиотекой numpy
- Визуализация данных с matplotlib
Несколько конструкторов с разным количеством аргументов
В данном случае можно создавать экземпляры класса только со вторым констуктором, в то время как запись x = a(42) вызовет ошибку.
Отслеживать
51.4k 86 86 золотых знаков 267 267 серебряных знаков 505 505 бронзовых знаков
задан 13 фев 2013 в 19:16
119 1 1 золотой знак 3 3 серебряных знака 10 10 бронзовых знаков4 ответа 4
Сортировка: Сброс на вариант по умолчанию
Если быть кратким то НЕТ.
Более детально посмотрите обсуждение вот ЗДЕСЬ. Там и варианты решений есть такие как использование необязательных или ключевых аргументов.
Отслеживать
3 4 4 бронзовых знака
ответ дан 13 фев 2013 в 19:21
3,724 14 14 серебряных знаков 16 16 бронзовых знаковВ Python’е перегрузок функций (а конструктор, по факту — это функция) нет в принципе. В вашем случае, вызывается всегда второй конструктор, ибо инструкция def означает «создать объект функции и присвоить этот объект написанному имени», то есть, сначала вы присваиваете имени __init__ одну функцию (с параметром b ), а затем — тому же имени — другую функцию, уже без параметра b .
Поведение, которого вы добиваетесь, можно получить, используя параметры по умолчанию, например:
def __init__(self, b=None): # Здесь лучше придумать что-то отличное от проверки на None # больше соответствующее вашей задаче. Возможно, вам просто подойдет # какое-то значение по умолчанию. if b is None: pass else: pass # . a = a() # b будет равно None по умолчанию b = a(42) # b будет равно 42Реализация нескольких конструкторов в Python
Часто при разработке программ на Python возникает ситуация, когда необходимо создать несколько конструкторов для одного класса. Это может быть полезно, например, когда у класса есть несколько полей, и нужно предоставить различные способы инициализации этих полей.
Рассмотрим пример. Пусть имеется класс Book , у которого есть свойства title и author . В некоторых случаях вам может потребоваться создать книгу, указав оба этих свойства. В других случаях вы может хотеть создать книгу без автора.
В Python метод __init__ класса выступает в роли конструктора, однако Python не поддерживает несколько методов __init__ в одном классе. В таком случае обычный подход — использовать значения по умолчанию для аргументов конструктора.
class Book: def __init__(self, title, author=None): self.title = title self.author = author
В этом примере, если при создании объекта класса Book не указывается автор, то свойство author автоматически получает значение None .
book1 = Book("War and Peace", "Leo Tolstoy") book2 = Book("1984")В данном случае, книга book1 будет создана с указанным автором, а книга book2 – без автора.
Это наиболее часто используемый подход для реализации «нескольких конструкторов» в Python. Он прост, чист и «pythonic».
Конструктор класса – метод __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 — это процедурно-ориентированный и одновременно объектно-ориентированный язык программирования.
Процедурно-ориентированный
«Процедурно-ориентированный» подразумевает наличие функций. Программист может создавать функции, которые затем используются в сторонних скриптах.
Объектно-ориентированный
«Объектно-ориентированный» подразумевает наличие классов. Есть возможность создавать классы, представляющие собой прототипы для будущих объектов.
Создание класса в Python
Синтаксис для написания нового класса:
class ClassName: 'Краткое описание класса (необязательно)' # Код .- Для создания класса пишется ключевое слово class , его имя и двоеточие (:). Первая строчка в теле класса описывает его. (По желанию) получить доступ к этой строке можно с помощью ClassName.__doc__
- В теле класса допускается объявление атрибутов, методов и конструктора.
Атрибут:
Атрибут — это элемент класса. Например, у прямоугольника таких 2: ширина ( width ) и высота ( height ).
Метод:
- Метод класса напоминает классическую функцию, но на самом деле — это функция класса. Для использования ее необходимо вызывать через объект.
- Первый параметр метода всегда self (ключевое слово, которое ссылается на сам класс).
Конструктор:
- Конструктор — уникальный метод класса, который называется __init__ .
- Первый параметр конструктора во всех случаях self (ключевое слово, которое ссылается на сам класс).
- Конструктор нужен для создания объекта.
- Конструктор передает значения аргументов свойствам создаваемого объекта.
- В одном классе всегда только один конструктор.
- Если класс определяется не конструктором, Python предположит, что он наследует конструктор родительского класса.
# Прямоугольник. class Rectangle : 'Это класс Rectangle' # Способ создания объекта (конструктор) def __init__(self, width, height): self.width= width self.height = height def getWidth(self): return self.width def getHeight(self): return self.height # Метод расчета площади. def getArea(self): return self.width * self.heightСоздание объекта с помощью класса Rectangle:

# Создаем 2 объекта: r1 & r2 r1 = Rectangle(10,5) r2 = Rectangle(20,11) print("r1.width token punctuation">, r1.width) print("r1.height token punctuation">, r1.height) print("r1.getWidth() token punctuation">, r1.getWidth()) print("r1.getArea() token punctuation">, r1.getArea()) print("-----------------") print("r2.width token punctuation">, r2.width) print("r2.height token punctuation">, r2.height) print("r2.getWidth() token punctuation">, r2.getWidth()) print("r2.getArea() token punctuation">, r2.getArea())
Что происходит при создании объекта с помощью класса?
При создании объекта класса Rectangle запускается конструктор выбранного класса, и атрибутам нового объекта передаются значения аргументов. Как на этом изображении:

Конструктор с аргументами по умолчанию
В других языках программирования конструкторов может быть несколько. В Python — только один. Но этот язык разрешает задавать значение по умолчанию.
Все требуемые аргументы нужно указывать до аргументов со значениями по умолчанию.
class Person: # Параметры возраста и пола имеют значение по умолчанию. def __init__(self, name, age=1, gender="Male"): self.name = name self.age = age self.gender= gender def showInfo(self): print("Name: ", self.name) print("Age: ", self.age) print("Gender: ", self.gender)from person import Person # Создать объект Person. aimee = Person("Aimee", 21, "Female") aimee.showInfo() print(" --------------- ") # возраст по умолчанию, пол. alice = Person( "Alice" ) alice.showInfo() print(" --------------- ") # Пол по умолчанию. tran = Person("Tran", 37) tran.showInfo()
Сравнение объектов
В Python объект, созданный с помощью конструктора, занимает реальное место в памяти. Это значит, что у него есть точный адрес.
Если объект AA — это просто ссылка на объект BB , то он не будет сущностью, занимающей отдельную ячейку памяти. Вместо этого он лишь ссылается на местоположение BB .

Оператор == нужен, чтобы узнать, ссылаются ли два объекта на одно и то же место в памяти. Он вернет True , если это так. Оператор != вернет True , если сравнить 2 объекта, которые ссылаются на разные места в памяти.
from rectangle import Rectangle r1 = Rectangle(20, 10) r2 = Rectangle(20 , 10) r3 = r1 # Сравните r1 и r2 test1 = r1 == r2 # --> False # Сравните r1 и r3 test2 = r1 == r3 # --> True print ("r1 == r2 ? ", test1) print ("r1 == r3 ? ", test2) print (" -------------- ") print ("r1 != r2 ? ", r1 != r2) print ("r1 != r3 ? ", r1 != r3)
Атрибуты
В Python есть два похожих понятия, которые на самом деле отличаются:
- Атрибуты
- Переменные класса
Стоит разобрать на практике:
class Player: # Переменная класса minAge = 18 maxAge = 50 def __init__(self, name, age): self.name = name self.age = ageАтрибут
Объекты, созданные одним и тем же классом, будут занимать разные места в памяти, а их атрибуты с «одинаковыми именами» — ссылаться на разные адреса. Например:

from player import Player player1 = Player("Tom", 20) player2 = Player("Jerry", 20) print("player1.name token punctuation">, player1.name) print("player1.age token punctuation">, player1.age) print("player2.name token punctuation">, player2.name) print("player2.age token punctuation">, player2.age) print(" ------------ ") print("Assign new value to player1.age = 21 ") # Присвойте новое значение атрибуту возраста player1. player1.age = 21 print("player1.name token punctuation">, player1.name) print("player1.age token punctuation">, player1.age) print("player2.name token punctuation">, player2.name) print("player2.age token punctuation">, player2.age)
Python умеет создавать новые атрибуты для уже существующих объектов. Например, объект player1 и новый атрибут address .
from player import Player player1 = Player("Tom", 20) player2 = Player("Jerry", 20) # Создайте новый атрибут с именем «address» для player1. player1.address = "USA" print("player1.name token punctuation">, player1.name) print("player1.age token punctuation">, player1.age) print("player1.address token punctuation">, player1.address) print(" ------------------- ") print("player2.name token punctuation">, player2.name) print("player2.age token punctuation">, player2.age) # player2 е имеет атрибута 'address' (Error!!) print("player2.address token punctuation">, player2.address)player1.name = Tom player1.age = 20 player1.address = USA ------------------- player2.name = Jerry player2.age = 20 Traceback (most recent call last): File "C:/Users/gvido/class.py", line 27, in print("player2.address атрибуты-функции"> Атрибуты функцииОбычно получать доступ к атрибутам объекта можно с помощью оператора «точка» (например,
player1.name). Но Python умеет делать это и с помощью функции.Функция Описание getattr (obj, name[,default])Возвращает значение атрибута или значение по умолчанию, если первое не было указано hasattr (obj, name)Проверяет атрибут объекта — был ли он передан аргументом «name» setattr (obj, name, value)Задает значение атрибута. Если атрибута не существует, создает его delattr (obj, name)Удаляет атрибут from player import Player player1 = Player("Tom", 20) # getattr(obj, name[, default]) print("getattr(player1,'name') token punctuation">, getattr(player1,"name")) print("setattr(player1,'age', 21): ") # setattr(obj,name,value) setattr(player1,"age", 21) print("player1.age token punctuation">, player1.age) # Проверка, что player1 имеет атрибут 'address'? hasAddress = hasattr(player1, "address") print("hasattr(player1, 'address') ? ", hasAddress) # Создать атрибут 'address' для объекта 'player1' print("Create attribute 'address' for object 'player1'") setattr(player1, 'address', "USA") print("player1.address token punctuation">, player1.address) # Удалить атрибут 'address'. delattr(player1, "address")getattr(player1,'name') = Tom setattr(player1,'age', 21): player1.age = 21 hasattr(player1, 'address') ? False Create attribute 'address' for object 'player1' player1.address = USAВстроенные атрибуты класса
Объекты класса — дочерние элементы по отношению к атрибутам самого языка Python. Таким образом они заимствуют некоторые атрибуты:
Атрибут Описание __dict__ Предоставляет данные о классе коротко и доступно, в виде словаря __doc__ Возвращает строку с описанием класса, или None , если значение не определено __class__ Возвращает объект, содержащий информацию о классе с массой полезных атрибутов, включая атрибут __name__ __module__ Возвращает имя «модуля» класса или __main__ , если класс определен в выполняемом модуле. class Customer: 'Это класс Customer' def __init__(self, name, phone, address): self.name = name self.phone = phone self.address = address john = Customer("John",1234567, "USA") print ("john.__dict__ token punctuation">, john.__dict__) print ("john.__doc__ token punctuation">, john.__doc__) print ("john.__class__ token punctuation">, john.__class__) print ("john.__class__.__name__ token punctuation">, john.__class__.__name__) print ("john.__module__ token punctuation">, john.__module__)john.__dict__ = john.__doc__ = Это класс Customer john.__class__ = john.__class__.__name__ = Customer john.__module__ = __main__Переменные класса
Переменные класса в Python — это то же самое, что Field в других языках, таких как Java или С#. Получить к ним доступ можно только с помощью имени класса или объекта.
Для получения доступа к переменной класса лучше все-таки использовать имя класса, а не объект. Это поможет не путать «переменную класса» и атрибуты.

У каждой переменной класса есть свой адрес в памяти. И он доступен всем объектам класса.
from player import Player player1 = Player("Tom", 20) player2 = Player("Jerry", 20) # Доступ через имя класса. print ("Player.minAge token punctuation">, Player.minAge) # Доступ через объект. print("player1.minAge token punctuation">, player1.minAge) print("player2.minAge token punctuation">, player2.minAge) print(" ------------ ") print("Assign new value to minAge via class name, and print..") # Новое значение minAge через имя класса Player.minAge = 19 print("Player.minAge token punctuation">, Player.minAge) print("player1.minAge token punctuation">, player1.minAge) print("player2.minAge token punctuation">, player2.minAge)Player.minAge = 18 player1.minAge = 18 player2.minAge = 18 ------------ Assign new value to minAge via class name, and print.. Player.minAge = 19 player1.minAge = 19 player2.minAge = 19Составляющие класса или объекта
В Python присутствует функция dir , которая выводит список всех методов, атрибутов и переменных класса или объекта.
from player import Player # Вывести список атрибутов, методов и переменных объекта 'Player' print(dir(Player)) print("\n\n") player1 = Player("Tom", 20) player1.address ="USA" # Вывести список атрибутов, методов и переменных объекта 'player1' print(dir(player1))['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'maxAge', 'minAge'] ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'address', 'age', 'maxAge', 'minAge', 'name']