функциональная разница между экземпляром и объектом, созданным не через __init__?
Слово self используется для обозначения экземпляра класса. Используя ключевое слово self , мы получаем доступ к атрибутам и методам класса в Python.
__init__ method
__init__ — это метод в классах Python. В объектно-ориентированной терминологии он называется конструктором. Этот метод вызывается, когда объект создается из класса, и позволяет классу инициализировать атрибуты класса.
# Узнайте стоимость прямоугольного поля class Rectangle: def __init__(self, length, breadth, unit_cost=0): self.length = length self.breadth = breadth self.unit_cost = unit_cost def get_area(self): return self.length * self.breadth def calculate_cost(self): area = self.get_area() return area * self.unit_cost # breadth = 120, length = 160, 1 sq unit cost = 2000 руб. rectangle = Rectangle(160, 120, 2000) print(f"Площадь прямоугольника: ")
Отслеживать
ответ дан 4 дек 2020 в 13:17
73.6k 110 110 золотых знаков 38 38 серебряных знаков 55 55 бронзовых знаков
Магический метод __init__ запускается при создании экземпляра класса. Пример:
class A(): def __init__(self): print('Hello, world!') a = A()
Hello, world!
Отслеживать
ответ дан 4 дек 2020 в 13:14
USERNAME GOES HERE USERNAME GOES HERE
10.4k 21 21 золотой знак 25 25 серебряных знаков 53 53 бронзовых знака
__init__ вызывается уже после создания экземпляра класса, а не во время, в примере ниже видно что сначала выполнится print(1, ob), и лишь затем print(2, self), причем объект экземпляра класса создан уже до вызова __init__ , и затем просто передается ему в аргумента в self
class A(): def __new__(cls, *args, **kwargs): ob = super().__new__(cls) print(1, ob) return ob def __init__(self): print(2, self) a = A() # 1 # 2
в примере ниже видно, что __init__ необязательный метод, и атрибуты класса можно создавать где угодно и когда угодно, хотя это конечно и считается неправильным подходом
class A(): def test(self): self.b = 123 a = A() a.test() print(a.b) # 123
в примере ниже видно, что self это не ключевое слово, это просто обычный аргумент у метода и его общепринятое название, но это аргумент который идет первым, ничто не мешает нам назвать его иначе, хотя это конечно и считается неправильным подходом
class A(): def __init__(qwerty): qwerty.b = 123 a = A() print(a.b) # 123
в примере ниже видно, что в исходном классе A , все его методы print(A.test) это обычные функции (function), которые лежат в области видимоcти A , а все методы его объекта a print(a.test) это методы(method), которые лежат в новой области видимости a , причем эти методы, это просто специально «декорированные» исходные функции объекта A print(a.test.__func__) , «декорирование» которых придает функционал необязательности указания первого аргумента self при вызове методов как a.test(123), причем нам ничего не мешает обращаться к методу и через исходную функцию A.test(a, 123), но уже передавая первый аргумент self
class A(): b = 321 def test(self, v): print(v) print(A.test) # a = A() print(a.test) # > print(a.test.__func__) # a.test(123) # 123 A.test(a, 123) # 123
Заметки об объектной системе языка 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__() всегда вызывается после __new__() в Python?
При создании объекта в Python, например экземпляра класса, происходит два важных этапа: создание экземпляра и его инициализация. Первый этап выполняется методом new(), а второй — init().
class MyClass: def __new__(cls): print("Метод __new__ вызван") instance = super().__new__(cls) return instance def __init__(self): print("Метод __init__ вызван") obj = MyClass()
Метод __new__ вызван Метод __init__ вызван
Метод new() вызывается при создании нового экземпляра класса. Он отвечает за выделение памяти под новый объект и возвращает этот объект. new() является статическим методом и должен возвращать экземпляр класса.
С другой стороны, метод init() вызывается после new(), когда объект уже создан. Он отвечает за инициализацию этого объекта, то есть задаёт начальные значения его атрибутов.
Следовательно, init() всегда вызывается после new(), так как объект должен быть создан перед тем, как он будет инициализирован.
Это поведение является стандартным для Python и в большинстве случаев не требует изменений. Однако, если по какой-то причине требуется изменить этот процесс, можно переопределить методы new() и init(). Например, можно предотвратить вызов init() после new() путём возвращения другого объекта из метода new().
Чем init() отличается от new()
Основное различие между этими двумя методами состоит в том, что __new__ обрабатывает создание объекта, а __init__ обрабатывает его инициализацию.
__new__ вызывается автоматически при вызове имени класса (при создании экземпляра), тогда как __init__ вызывается каждый раз, когда экземпляр класса возвращается __new__ , передавая возвращаемый экземпляр в __init__ в качестве параметра self , поэтому даже если вы сохранили экземпляр где-нибудь глобально/статически и возвращали его каждый раз из __new__ , для него все-равно будет каждый раз вызываться __init__ .
Из вышесказанного вытекает что сначала вызывается __new__ , а потом __init__
Примеры ответов:
- PYTHON JUNIOR интервью со SpaciX
- Собеседование на позицию Backend Dev…
- Собеседование на позицию Backend Dev…
- Собеседование Junior Python Developer
- Собеседование Trainee Python Develop…
- Собеседование Trainee Python Develop…
- Собеседование Trainee Python Develop…
- Собеседование Trainee Python Develop…
- Собеседование Python 2023. Разбор ба…
- Моковое собеседование на позицию Dja…