Конструктор класса – метод __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
6 рекомендаций по определению метода __init__

Основным краеугольным камнем Python как объектно-ориентированного языка программирования является определение связанных классов для управления и обработки данных. Когда мы создаем класс, первым методом, который определяем является метод инициализации __init__ . Если вы примените следующие рекомендации, то тот, кто будет читать ваш код, лучше поймет механику работы всех объектов экземпляра класса. В этой статье я хочу рассказать вам о рекомендациях по определению метода __init__ .
1. Располагайте его в верхней части класса
Для большинства из нас эта рекомендация прозвучит очевидно, но я видел, как люди прячут его глубоко в теле класса вместе с другими атрибутами. Важно поместить его в самом начале, перед любыми другими методами. Именно здесь те, кто будут читать код, должны искать __init__ . Если у вас есть атрибуты, то метод __init__ следует поместить после них. Следовать этому правилу нужно для всех классов, которые вы пишете в своем проекте, чтобы читающие не путались.
2. Называйте первый параметр self
Прежде всего, вы должны понимать, за что отвечает первый параметр. Он относится к объекту экземпляра, который вызывает метод __init__ . Возможно, вы слышали термин «инстанцирование», однако метод __init__ сам по себе не эквивалентен ему. Как следует из названия, __init__ означает инициализацию, которая отвечает за процесс установки начального состояния созданного экземпляра класса. Есть соглашение, согласно которому первый параметр должен называться self , хотя это и не обязательно. Отмечу, что self – это не ключевое слово в Python, в отличие от многих других языков, где используется this , self или it . Они являются зарезервированными ключевыми словами для ссылки на текущий вызываемый экземпляр.
3. Задайте все атрибуты экземпляра
Python не ограничивает вас в месте определения атрибутов экземпляра класса. Однако это не значит, что вы можете писать их где угодно. Рассмотрим следующий пример:
class Student: def __init__(self, name): self.name = name def verify_registration(self): self.registered = True def get_guardian_name(self): self.guardian = "Someone"
Как показано выше, мы можем создать экземпляр класса Student , инициализировав его имя. Затем мы можем вызвать verify_registration , чтобы получить статус регистрации и get_guardian_name , чтобы получить информацию об опекуне учащегося. Однако так делать нежелательно, поскольку те, кто будут читать ваш код, не будут уверены в том, какие атрибуты есть у экземпляра. Вместо этого лучше поместить все атрибуты в __init__ , чтобы читатели точно знали, какие атрибуты есть у экземпляров. В следующей реализации шаблон гораздо лучше:
class Student: def __init__(self, name): self.name = name self.registered = False self.guardian = None
4. Старайтесь не использовать **kwargs
В Python **kwargs используется для указания различного числа именованных параметров в определениях функций. Таким образом синтаксически правильнее включать **kwargs в __init__ . Конечно, существуют обстоятельства, при которых использовать **kwargs в __init__ допустимо, но в большинстве случаев это не нужно, поскольку так вы маскируете необходимые параметры для инициализации экземпляров.Предполагаю, что одним из основных оправданий использования **kwargs является «чистота» __init__ . Однако я считаю, что явное всегда лучше, чем неявное. Несмотря на то, что перечисление всех параметров в заголовке метода __init__ может показаться громоздким, мы четко даем понять, какие параметры пользователю необходимо указать при создании экземпляров класса.
5. Устанавливайте правильные значения по умолчанию
Если вы знаете, какие начальные значения должны быть у определенных атрибутов, вы можете указать их в заголовке __init__ , чтобы пользователи не задавали эти параметры при создании экземпляров. В качестве альтернативы, если определенные значения применимы к большинству сценариев создания экземпляров, вы также можете задать эти параметры. Например:
class Student: def __init__(self, name, bus_rider=True): self.name = name self.bus_rider = bus_rider
Однако следует отметить, что, если параметр – это изменяемая структура данных, вам придется выбрать другой путь. Вот плохой пример:
class Student: def __init__(self, name, subjects=["maths", "physics"]): self.name = name self.subjects = subjects
Проблема в том, что, если вы указываете [“maths”, “physics”] как значение по умолчанию, этот список будет создан в определении функции и его же будут использовать все экземпляры. Вот как выглядит эта проблема:
>>> student0 = Student("John") >>> student0.subjects.append("music") >>> student0.subjects ['maths', 'physics', 'music'] >>> student1 = Student("Ashley") >>> student1.subjects ['maths', 'physics', 'music']
Документация
Как и в случае с другими методами, у __init__ тоже должна быть документация. Несмотря на то, что некоторым людям не нравится документировать информацию об архитектуре на уровне класса (то есть помещать информацию об инициализации под заголовком класса), все же рекомендуется размещать документацию прямо под методом __init__ . Для каждого параметра вы указываете его тип – будь то str или int . Исходя из этого, вы даете краткое описание того, что этот параметр из себя представляет и что он делает. Если есть значение по умолчание, расскажите о нем сопроводив доступным пояснением.
Заключение
Вот так выглядит список рекомендаций, которым я следую при определении метода __init__ для классов в Python. Лично я нахожу их полезными для поддержания читаемости кода. Если вам есть, что добавить, расскажите об этом в комментариях. Спасибо, что прочитали.
Материал подготовлен в рамках курса «Python Developer. Basic».
Всех желающих приглашаем на онлайн-интенсив «Мобильное приложение для автоматических рассылок с использованием Kivy Framework». За 2 дня интенсива мы создадим мобильное приложение (с использованием Kivy Framework) для планирования автоматических рассылок почтовых сообщений.
- python
- __init__
- метод __init__
- data science
- kivy framework
- создание мобильного приложения
- Блог компании OTUS
- Python
- Программирование
Заметки об объектной системе языка Python ч.2
Вторая часть заметок об объектной системе python’a (первая часть тут). В этой статье рассказывается, что такое классы, метаклассы, type, object и как происходит поиск атрибутов в классе.
Классы
Классы (типы) — это объектные фабрики. Их главная задача — создавать объекты, обладающие определенным поведением.
Классы определяют поведение объектов с помощью своих атрибутов (которые хранятся в __dict__ класса): методов, свойств, классовых переменные, дескрипторов, а также с помощью атрибутов, унаследованных от родительских классов.
Инстанцирование обычного объекта происходит в 2 этапа: сначала его создание, потом инициализация. Соответственно, сначала запускается метод класса __new__, который возвращает объект данного класса, потом выполняется метод класса __init__, который инициализирует уже созданный объект.
def __new__(cls, . ) — статический метод (но его можно таковым не объявлять), который создает объект класса cls.
def __init__(self, . ) — метод класса, который инициализирует созданный объект.
Например, объявим класс:
>>> class A ( object ):
. pass
.
>>>
Для класса A не определены ни __new__, ни __init__. В соответствии с алгоритмом поиска атрибутов для класса (типа), который не стоит путать с алгоритмом поиска атрибутов для обычных объектов, когда класс не найдет их в своем__dict__, он будет искать эти методы в __dict__ своих базовых (родительских) классах.
Класс А имеет в качестве родителя встроенный класс object. Таким образом он будет их искать в object.__dict__
Раз есть такие методы, значит, получается, что a = A() аналогичен последовательности вызовов:
a = object.__new__(A)
object.__init__(a)
В общем виде, используя super, который как раз и реализует алгоритм поиска атрибутов по родительским классам [1]:
a = super(A, A).__new__(A)
super(A, A).__init__(a)
>>> class A ( object ):
. def __new__ (cls):
. obj = super (A, cls) . __new__(cls)
. print ‘created object’ , obj
. return obj
. def __init__ ( self ):
. print ‘initing object’ , self
.
>>> A()
created object
initing object
>>>
Singleton v.1
Понимая, как происходит создание объекта, можно написать реализацию паттерна одиночка.
Мы должны гарантировать, что у класса есть только один экземпляр. Т.е. при вызове конструктора класса, всегда возвращаем один и тот же экземпляр класса.
А это значит, что при вызов метода __new__ должен возвращать каждый раз один и тот же объект. Хранить сам объект можно, например, в классовой переменной instance.
В результате получаем:
>>> class C ( object ):
. instance = None
. def __new__ (cls):
. if cls . instance is None :
. cls . instance = super (C, cls) . __new__(cls)
. return cls . instance
.
>>> C() is C()
True
>>> C() . x = 1
>>> c = C()
>>> d = C()
>>> c . x
1
>>> d . x
1
>>> c . x =2
>>> d . x
2
>>> c . x
2
Классы и метаклассы.
Для класса (типа), так же как и для обычного объекта, существует класс (тип), который создает классы и определяет поведение класса. Этот класс называется метаклассом.
Создание класса, как и обычного объекта происходит с помощью вызова конструктора, но т.к. в классе есть несколько дополнительных специальных атрибутов, которые должны быть инициализированы, в конструктор передаются и соответствующие обязательные параметры.
XClass = XMetaClass(name, bases, attrs)
Тогда, сразу после создания
XClass.__name__ равно name,
XClass.__bases__ равен bases,
XClass.__dict__ равен attrs, а
XClass.__class__ равен XMetaClass
По умолчанию для всех определяемых классов метаклассом является type.
>>> class A ( object ):
. pass
.
Эквивалентно, по аналогии с обычными объектами:
>>> type ( ‘A’ , ( object ,), <>)
>>> class B (A):
. def foo ( self ):
. 42
.
При определении класса, можно задать свой метакласс с помощью
классовой переменной __metaclass__:
>>> class A ( object ):
. __metaclass__ = Meta
.
>>>
Что равносильно: A = Meta(‘A’, (object,), <>)
О type и object
Прежде всего type и object — это объекты. И, как у всех порядочных объектов, у них есть специальные атрибуты __class__ и __dict__:
>>> object . __class__
>>> type . __class__
>>> object . __dict__
>>> type . __dict__
Более того object, и type — это объекты типа (классы), и у них тоже есть специальные атрибуты __name__, __bases___:
>>> object . __name__
‘object’
>>> type . __name__
‘type’
>>> object . __bases__
()
>>> type . __bases__
(,)
>>>
Экземпляры типа или класса object — это объекты (любые). Т.е. любой объект — экземпляр класса object:
>>> isinstance ( 1 , object )
True
>>> isinstance ( setattr , object )
True
>>> isinstance ( «foo» , object )
True
>>> isinstance (A, object )
True
Даже функция является объектом:
>>> def bar ():
. pass
.
>>> isinstance (bar, object )
True
Кроме того, класс object сам является своим экземпляром:
>>> isinstance ( object , object )
True
type тоже является его экземпляром:
>>> isinstance ( type , object )
True
Инстанцирование — object() возвращает самый простой и общий объект:
>>> o = object ()
У которого даже __dict__ нет, есть только __class__.
>>> o . __dict__
Traceback (most recent call last):
File «», line 1, in
AttributeError: ‘object’ object has no attribute ‘__dict__’
>>> o . __class__
>>>
Экземпляры класса или типа type — это только другие классы или другие типы:
Число — это не класс
>>> isinstance ( 1 , type )
False
>>> isinstance ( «foo» , type )
False
Встроенная функция setattr тоже не класс.
>>> isinstance ( setattr , type )
False
Класс — это класс.
>>> isinstance (A, type )
True
Тип строки — это класс.
>>> isinstance ( «foo» . __class__, type )
True
Т.к. object и type — тоже классы, то они являются экземплярами класса type:
>>> isinstance ( object , type )
True
>>> isinstance ( type , type )
True
>>>
Т.к. множество классов (типов) являются подмножеством множества объектов, то логично предположить, что type является подклассом object, т.е.
>>> issubclass ( type , object )
True
>>> issubclass ( object , type )
False
type — это просто класс, экземплярами которого являются другие классы. (т.е. метакласс). А сами классы можно считать расширением простых, обычных объектов.
Таким образом, когда мы наследуем класс от object, этот класс автоматически наследует поведение класса object, т.е. при инстанцировании он будет возвращать обычный объект. А когда мы наследуем от класса type, мы также автоматически наследуем поведение класса type, т.е. при инстацированни будет создаваться класс. А класс, который создает класс, называется метаклассом.
Значит, чтобы определить просто класс, нужно наследовать его от object, чтобы определить метакласс — наследуем его от type.
И еще: не нужно путать type(a) и type(name, bases, attrs).
type(a) — вызов с одним аргументом, возвращает тип объекта,
a type(name, bases, attrs) — вызов с тремя аргументами — это вызов конструктора класса.
О поиске атрибутов в классе
Как уже было отмечено, алгоритм поиска атрибутов в обычном объекте, но есть некоторые тонкости, т.к. у типов (классов) есть __bases__ — родительские классы (типы).
Если атрибут есть в __dict__ возвращается он, затем идет поиск по базовым классам из __bases__, а потом идет обращение к __dict__ __class__’а (т.е. фактически метакласса) и его (метакласса) родительских классов (метаклассов).
>>> class Ameta ( type ):
. def foo (cls):
. print ‘Ameta.foo’
.
>>> class A ( object ):
. __metaclass__ = Ameta
.
>>> A . foo()
Ameta.foo
Все что определяется в метаклассе доступно для класса, но не доступно для экзмепляров класса — обычных объектов, т.к. поиск атрибутов в обычном объекте ведется только по __dict__ словарям класса.
>>> a = A()
>>> a . foo
Traceback (most recent call last):
File «» , line 1 , in
AttributeError : ‘A’ object has no attribute ‘foo’
В A.__dict__ ‘foo’ нет:
>>> A . __dict__[ ‘foo’ ]
Traceback (most recent call last):
File «» , line 1 , in
KeyError : ‘foo’
Зато он есть в метаклассе, поэтому:
>>> A . foo()
Ameta.foo
>>> class B (A):
. @classmethod
. def foo (cls):
. print ‘B.foo’
.
>>> B . foo # т.к. foo есть B.__dict__ вернется значение B.__dict__[‘foo’]
>
>>> B . foo()
B.foo
>>> class C (B):
. pass
.
>>> C . foo() # вернет значение из базового класса B.
B.foo
Экземпляр класса C также вызовет метод foo из класса B.
>>> c = C()
>>> c . foo()
B.foo
>>> class D (A):
. pass
.
>>> D . foo()
Ameta.foo
А экземпляр D не найдет:
>>> d = D()
>>> d . foo()
Traceback (most recent call last):
File «» , line 1 , in
AttributeError : ‘D’ object has no attribute ‘foo’
Метаклассы
Метаклассы являются фабриками классов (или типов). Инстанцирование класса тоже проходит в 2 этапа — создание объекта типа (класса) и его инициализация. Это также делается с помощью двух методов метакласса. Сначала вызывается метод __new__ метакласса с параметрами, необходимыми для создания класса — name, bases, attrs, а потом __init__ с теми же параметрами и уже созданным классом.
>>> class Meta ( type ):
. pass
.
>>> Meta( ‘A’ , ( object ,), <>)
В начале метакласс Meta ищет метод __new__ у себя в словаре __dict__, не находит его там и начинает искать в __dict__ своих родительских классах (т.е. метаклассах, в данном случае type), т.е. происходит обычный поиск атрибута в классе. В результате исполнения __new__ с соответствующими параметрами получает новый класс, который потом инициализируется вызовом __init__ метода метакласса.
В совсем развернутом виде получается:
cls = type.__dict__[‘__new__’](Meta, ‘A’, (object,), <>)
type.__dict__[‘__init__’](cls, ‘A’, (object,), <>)
Или с помощью super
cls = super(Meta, Meta).__new__(Meta, ‘A’, (object,), <>)
super(Meta, Meta).__init__(cls, ‘A’, (object,), <>)
Стоит отметить, что в отличие от инстанцирования обычных объектов, используется не object.__new__ и object.__init__, а type.__new__ и type.__init__. У object.__new__ и type.__new__ разные сигнатуры, и object.__new__ возвращает обычный объект (regular object), а type.__new__ — объект типа (typeobject), т.е. класс.
Посмотрим, как это все работает на примере.
>>> class Meta ( type ):
. def __new__ (mcls, name, bases, attrs):
. print ‘creating new class’ , name
. return super (Meta, mcls) . __new__(mcls, name, bases, attrs)
. def __init__ (cls, name, bases, attrs):
. print ‘initing new class’ , name
.
.
>>> class A ( object ):
. __metaclass__ = Meta
.
creating new class A
initing new class A
Во время инстанцирования просто объекта, никаких надписей не выводится.
>>> a = A()
>>>
Кроме того, соответственно, во методах __new__ и __init__ метакласса можно менять все: имя, список суперклассов, атрибуты.
Cсылки
- Unifying types and classes in Python — главный документ, объясняющий что, как и зачем в новых классах.
- Making Types Look More Like Classes — PEP 252, описывающий отличие старых классов от новых.
- Built-in functions — детальное описание работы всех встроенных функций.
- Data model — детальное описание модели данных python’а.
- Python types and objects — объяснение объектной модели python на простых примерах с картинками.
Примечания
[1] Более подробно про super — тут.
Разбираемся с __init__ и self в Python
Одни из основных концепций, которые новички встречают при изучении Python и особенно объектно-ориентированного программирования (ООП), это __init__ и self.
Алексей Кодов
Автор статьи
7 июля 2023 в 17:27
Одни из основных концепций, которые новички встречают при изучении Python и особенно объектно-ориентированного программирования (ООП), это __init__ и self . Это ключевые элементы классов в Python, понимание которых является важным шагом в освоении языка.
Пример проблемы
Допустим, есть класс, который описывает сущность «Автомобиль».
class Car: def __init__(self, color, brand): self.color = color self.brand = brand
Здесь видим две незнакомые конструкции: __init__ и self . Что они делают?
Разбор init
__init__ — это специальная функция, которая вызывается при создании нового объекта класса. Она также известна как конструктор класса. Это место, где обычно устанавливаются начальные значения атрибутов класса.
В примере выше, __init__ принимает три аргумента: self , color и brand . color и brand — это значения, которые передаются при создании нового объекта класса Car , и они устанавливаются в качестве атрибутов объекта.
my_car = Car('blue', 'Toyota')
В этом случае ‘blue’ и ‘Toyota’ передаются в __init__ и устанавливаются в качестве атрибутов color и brand для my_car .
Разбор self
self — это ссылка на текущий экземпляр класса. Это способ обращения к атрибутам и методам класса изнутри самого класса.
В примере выше, self.color = color устанавливает атрибут color текущего объекта класса Car в значение color , переданное в __init__ .
Следует отметить, что self не передается при создании нового объекта класса. Python автоматически передает ссылку на текущий объект в self .
my_car = Car('blue', 'Toyota') # self не передается
В заключение, __init__ и self являются важными концепциями в Python, особенно в контексте ООП. __init__ используется для инициализации новых объектов класса, а self — для ссылки на текущий объект класса.