Использование метода .__new__() в классах Python
Примеры использования метода .__new__() при создании классов
При написании классов Python обычно нет необходимости создавать собственную реализацию специального метода .__new__() . В большинстве случаев базовой реализации из встроенного класса объектов достаточно для создания пустого объекта текущего класса.
Тем не менее, есть несколько интересных вариантов использования этого метода.
- Общий принцип использования метода .__new__() ;
- Создание подклассов неизменяемых встроенных типов;
- Метод .__new__() как фабрика случайных объектов;
- Создание «Singleton» класса.
Общий принцип использования метода .__new__() .
Обычно, создание собственной реализации метода .__new__() необходима только тогда, когда нужно управлять созданием нового экземпляра класса на низком уровне. Теперь, если нужна кастомная реализация этого метода, то следует выполнить несколько шагов:
- Создать новый экземпляр, вызвав super().__new__() с соответствующими аргументами.
- Настроить новый экземпляр в соответствии с конкретными потребностями.
- возвратить новый экземпляр, чтобы продолжить процесс создания экземпляра.
С помощью этих трех кратких шагов можно настроить этап создания экземпляра в процессе создания экземпляра Python.
class SomeClass: def __new__(cls, *args, **kwargs): instance = super().__new__(cls) # В этом месте можно настроить свой экземпляр. return instance def __init__(self, val): self.val = val
В этом примере представлена своего рода реализация шаблона .__new__() . Как обычно, .__new__() принимает текущий класс в качестве аргумента, который обычно называется cls .
Обратите внимание, что используются *args и **kwargs , чтобы сделать метод более гибким и удобным в сопровождении, принимая любое количество аргументов. Всегда необходимо определять метод .__new__() с помощью *args и **kwargs , если только нет веской причины следовать другому шаблону.
Вызов super().__new__(cls) необходим, чтобы получить доступ к методу object.__new__() родительского класса object , который является базовой реализацией метода .__new__() для всех классов Python. (Встроенный класс object является базовым классом по умолчанию для всех классов Python)
Важно отметить, что сама функция object.__new__() принимает только один аргумент — класс для создания экземпляра. Если вызывать object.__new__() с большим количеством аргументов, то получим исключение TypeError . Однако object.__new__() по-прежнему принимает и передает дополнительные аргументы в конструктор .__init__() , если класс не имеет собственной реализации .__new__() .
>>> a = SomeClass(7) >>> a.val # 7
Создание подклассов неизменяемых встроенных типов.
Начнем с варианта использования .__new__() , который состоит из подкласса неизменяемого встроенного типа. В качестве примера предположим, что необходимо написать класс Distance как подкласс типа float Python. У класса Distance будет дополнительный атрибут для хранения единицы, которая используется для измерения расстояния.
Первый подход к проблеме с использованием метода .__init__() :
class Distance(float): def __init__(self, value, unit): super().__init__(value) self.unit = unit >>> in_miles = Distance(42.0, "Miles") # Traceback (most recent call last): # . # TypeError: float expected at most 1 argument, got 2
Когда создается подкласс неизменяемого встроенного типа данных, то получаем ошибку. Часть проблемы в том, что значение задается при создании, а менять его при инициализации уже поздно. Кроме того, функция float.__new__() вызывается, как говориться «под капотом«, и она не обрабатывает дополнительные аргументы так же, как object.__new__() . Это то, что вызывает ошибку в этом примере.
Чтобы обойти эту проблему, можно инициализировать объект во время создания с помощью .__new__() вместо переопределения в .__init__() .
class Distance(float): def __new__(cls, value, unit): instance = super().__new__(cls, value) instance.unit = unit return instance >>> in_miles = Distance(42.0, "Miles") >>> in_miles # 42.0 >>> in_miles.unit # 'Miles' >>> in_miles + 42.0 # 84.0
В этом примере .__new__() выполняет три шага:
- метод создает новый экземпляр текущего класса cls , вызывая super().__new__() . На этот раз вызов возвращается к float.__new__() , который создает новый экземпляр и инициализирует его, используя значение в качестве аргумента.
- метод настраивает новый экземпляр, добавляя к нему атрибут .unit .
- возвращает новый экземпляр.
Теперь класс Distance работает, как и ожидалось, позволяя использовать атрибут экземпляра для хранения единиц измерения. В отличие от значения float , хранящегося в экземпляре Distance атрибут .unit является изменяемым.
Метод .__new__() фабрика случайных объектов.
Создание классов, возвращающие экземпляры другого класса может вызвать создание специальной реализации метода .__new__() . При создании классов подобного рода нужно быть осторожны, потому что в этом случае Python полностью пропускает этап инициализации. Таким образом, вы будете нести ответственность за перевод вновь созданного объекта в допустимое состояние, прежде чем использовать его в своем коде.
Следующий пример демонстрирует возврат экземпляров случайно выбранных классов:
# pets.py from random import choice class Pet: def __new__(cls): # выбираем класс случайным образом other = choice([Dog, Cat, Python]) # подставляем вместо собственного класса `cls` # случайно выбранный `other` instance = super().__new__(other) print(f"Я type(instance).__name__>!") return instance def __init__(self): print("Класс `Pet` никогда не запустится!") class Dog: def communicate(self): print("Гав! Гав!") class Cat: def communicate(self): print("Мяу! Мяу!") class Bird: def communicate(self): print("Чик! Чирик!")
В этом примере класс Pet предоставляет метод .__new__() , который создает новый экземпляр, случайным образом выбирая класс из списка существующих классов.
Использование класса Pet в качестве фабрики объектов домашних питомцев:
>>> from pets import Pet >>> pet = Pet() # Я Dog! >>> pet.communicate() # Гав! Гав! >>> isinstance(pet, Pet) # False >>> isinstance(pet, Dog) # True >>> another_pet = Pet() # Я Bird! >>> another_pet.communicate() # Чик! Чирик!
Каждый раз, когда создается экземпляр Pet , то в результате получается случайный объект из другого класса. Такое возможно, т. к. нет ограничений на объект, который может возвращать .__new__() . Использование .__new__() таким образом превращает класс в гибкую и мощную фабрику объектов, не ограниченную экземплярами самого себя.
Наконец, обратите внимание, что метод класса Pet.__init__() никогда не запускается. Это происходит потому, что Pet.__new__() всегда возвращает объекты другого класса, а не самого Pet .
Создание «Singleton» класса.
Иногда нужно реализовать класс, который позволяет создавать только один экземпляр. Этот тип класса широко известен как «Singleton«. В этой ситуации удобен метод .__new__() , потому что он может помочь ограничить количество экземпляров, которые может иметь данный класс.
Примечание. Большинство опытных разработчиков утверждают, что не нужно реализовывать шаблон проектирования «Singleton» в Python, если уже нет рабочего класса и нужно добавить функциональность шаблона поверх него. В остальных случаях можно использовать константу уровня модуля, чтобы получить ту же функциональность «Singleton» без необходимости писать относительно сложный класс.
Пример класса Singleton с использованием метода .__new__() , который позволяет создавать только один экземпляр за раз. Для реализации такого поведения, метод .__new__() проверяет наличие предыдущих экземпляров, кэшированных в атрибуте класса:
class Singleton: _instance = None def __new__(cls, *args, **kwargs): if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance >>> a = Singleton() >>> b = Singleton() >>> a is b # True
В этом примере класс Singleton имеет атрибут с именем ._instance , который по умолчанию имеет значение None и работает как кеш. Метод .__new__() проверяет, не существует ли предыдущий экземпляр, проверяя, что условие cls._instance равно None . Если это условие истинно, то блок кода if создает новый экземпляр Singleton и сохраняет его в cls._instance . Наконец, метод возвращает вызывающей стороне новый или существующий экземпляр.
Внимание! В приведенном выше примере Singleton не предоставляет реализацию .__init__() . Если когда-нибудь понадобится такой класс с методом .__init__() , то имейте в виду, что этот метод будет запускаться каждый раз, когда вы вызываете конструктор Singleton() . Такое поведение может вызвать непредсказуемые эффекты инициализации и ошибки.
- ОБЗОРНАЯ СТРАНИЦА РАЗДЕЛА
- Пространство имен и область видимости в классах
- Определение классов
- Объект класса и конструктор класса
- Создание экземпляра класса
- Метод экземпляра класса
- Что такое метод класса и зачем нужен
- Что такое статический метод в классах Python и зачем нужен
- Атрибуты класса и переменные экземпляра класса
- Кэширование методов экземпляра декоратором lru_cache
- Закрытые/приватные методы и переменные класса Python
- Наследование классов
- Множественное наследование классов
- Абстрактные классы
- Перегрузка методов в классе Python
- Что такое миксины и как их использовать
- Класс Python как структура данных, подобная языку C
- Создание пользовательских типов данных
- Специальные (магические) методы класса Python
- Базовая настройка классов Python магическими методами
- Настройка доступа к атрибутам класса Python
- Дескриптор класса для чайников
- Протокол дескриптора класса
- Практический пример дескриптора
- Использование метода .__new__() в классах Python
- Специальный атрибут __slots__ класса Python
- Специальный метод __init_subclass__ класса Python
- Определение метаклассов metaclass
- Эмуляция контейнерных типов в классах Python
- Другие специальные методы класса
- Как Python ищет специальные методы в классах
- Шаблон проектирования Фабрика и его реализация
Инициализация и протоколы — Python: Введение в ООП
Теперь, когда вы знакомы со связанными методами, настало время рассказать про самый важный метод в Python: метод __init__ («dunder-init», «дандер инит»). Этот метод отвечает за инициализацию экземпляров класса после их создания.
На прошлых уроках Бобу — экземпляру класса Person , мы задавали имя уже после того, как сам объект был создан. Такое заполнение атрибутов объекта и выглядит громоздко и может приводить к разного рода ошибкам: объект может какое-то время находиться в «недозаполненном» (более общее название — «неконсистентном») состоянии. Инициализатор же позволяет получить уже полностью настроенный экземпляр.
Реализуем класс Person так, чтобы имя можно было указывать при создании объекта:
class Person: def __init__(self, name): self.name = name bob = Person('Bob') bob.name # 'Bob' alice = Person('Alice') alice.name # 'Alice'
Теперь нет нужды иметь в классе атрибут name = ‘Noname’ , ведь все объекты получают имена при инстанцировании!
Методы и протоколы
Вы заметили, что Python сам вызывает метод __init__ в нужный момент? Это же касается большинства dunder-методов: таковые вы только объявляете, но вручную не вызываете. Такое поведение часто называют протоколом (или поведением): класс реализует некий протокол, предоставляя нужные dunder-методы. А Python, в свою очередь, работает с объектом посредством протокола.
Продемонстрирую протокол получения длины имени объекта:
class Person: def __init__(self, name): self.name = name def __len__(self): return len(self.name) tom = Person('Thomas') len(tom) # 6
Я объявил метод dunder-len, и объекты класса Person научились возвращать «свою длину» (да, пример надуманный, но суть отражает). Вызов функции len на самом деле приводит к вызову метода __len__ у переданного в аргументе объекта!
Протоколы и «утиная типизация»
Существует такой термин: «утиная типизация» («duck typing») — «Если что-то крякает как утка и плавает как утка, то возможно это утка и есть!». Данный термин, пусть и звучит как шутка, описывает важный принцип построения абстракций: коду практически всегда достаточно знать о значении, с которым этот код работает, что это значение обладает нужными свойствами. Все остальное — не существенно.
Если коду достаточно знать, что сущность умеет плавать и крякать, то код не должен проверять сущность на фактическую принадлежность к уткам. Это позволит коду работать с робо-утками, даже если все настоящие утки вымрут, ведь код работает с абстрактными утками!
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Конструктор класса – метод __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
функциональная разница между экземпляром и объектом, созданным не через __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.7k 112 112 золотых знаков 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