Как создать инициализатор класса someclass python
Перейти к содержимому

Как создать инициализатор класса someclass python

  • автор:

Объектно-ориентированное программирование. Специальные методы.

Декоратор @staticmethod определяет обычную функцию (статический метод) в пространстве имён класса. У него нет обязательного параметра-ссылки self. Может быть полезно для вспомогательных функций, чтобы не мусорить пространство имён модуля. Доступ к таким методам можно получить как из экземпляра класса, так и из самого класса:

class SomeClass(object): @staticmethod def hello(): print("Hello, world") SomeClass.hello() # Hello, world obj = SomeClass() obj.hello() # Hello, world 
Hello, world Hello, world

Декоратор @classmethod создаёт метод класса и требует обязательную ссылку на класс (cls). Поэтому объект класса явно передаётся через первый параметр как это с параметром self происходит для обычных методов. Также как и для self, переданный cls может отличаться от класса, в котором определён класс-метод (может быть потомок). Часто используется для создания альтернативных конструкторов.

class SomeClass(object): @classmethod def hello(cls): print('Hello, класс <>'.format(cls.__name__)) SomeClass.hello() # Hello, класс SomeClass 
Hello, класс SomeClass

Давайте взглянем на пример кода, в котором одновременно показаны она декоратора, это может помочь понять основные принципы:

class Person: def __init__(self, name, age): self.name = name self.age = age # classmethod чтобы создать объект по году рождения, # "альтернативный" конструктор @classmethod def fromBirthYear(cls, name, year): return cls(name, 2019 - year) # статический метод,чтобы проверить совершеннолетие @staticmethod def isAdult(age): return age > 18 person1 = Person('Петя', 21) person2 = Person.fromBirthYear('Петя', 1996) print(person1.age) print(person2.age) # print the result print(Person.isAdult(22)) 
21 23 True

Важно понимать, что ни classmethod ни staticmethod НЕ являются функциями от конкретного объекта класса и соответственно не принимают self. Подчеркнем еще раз их различия: — classmethod принимает cls как первый параметр, тогда как staticmethod в специальных аргументах не нуждается — classmethod может получать доступ или менять состояние класса, в то время как staticmethod нет — staticmethod в целом вообще ничего не знают про класс. Это просто функция над аргументами, объявленная внутри класса.

Специальные методы (магические) вида _ _

В Python существует огромное количество специальных методов, расширяющих возможности пользовательских классов. Например, можно определить вид объекта на печати, его «официальное» строковое представление или поведение при сравнениях.

Эти методы могут эмулировать поведение встроенных классов, но при этом они необязательно существуют у самих встроенных классов. Например, у объектов int при сложении не вызывается метод add. Таким образом, их нельзя переопределить.

Давайте для примера переопределим стандартную операцию сложения. Рассмотрим класс Vector, используемый для представления радиус-векторов на координатной плоскости, и определим в нем поля-координаты: x и y. Также очень хотелось бы определить для векторов операцию +, чтобы их можно было складывать столь же удобно, как и числа или строки.

Для этого необходимо перегрузить операцию +: определить функцию, которая будет использоваться, если операция + будет вызвана для объекта класса Vector. Для этого нужно определить метод add класса Vector, у которого два параметра: неявная ссылка self на экземпляр класса, для которого она будет вызвана (это левый операнд операции +) и явная ссылка other на правый операнд:

class Vector(): def __init__(self, x = 0, y = 0): self.x = x self.y = y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) A = Vector(1, 2) B = Vector(3, 4) C = A + B print(C.x, C.y) 

Теперь при вызове оператора A + B Питон вызовет метод A.add(B), то есть вызовет указанный метод, где self = A, other = B.

class Vector: def __lt__(self, other): return self.x  other.x or self.x == other.x and self.y  other.y 

В этом примере оператор вернет True, если у левого операнда поле x меньше, чем у правого операнда, а также если поля x у них равны, а поле y меньше у левого операнда.

Список основных перегружаемых операторов

Метод Использование
Операторы сравнения
__lt__(self, other) x < y
__le__(self, other) x
__eq__(self, other) x == y
__ne__(self, other) x != y
__gt__(self, other) x > y
__ge__(self, other) x >= y
Арифметические операторы
Сложение
__add__(self, other) x + y
__radd__(self, other) y + x
__iadd__(self, other) x += y
Вычитание
__sub__(self, other) x — y
__rsub__(self, other) y — x
__isub__(self, other) x -= y
Умножение
__mul__(self, other) x * y
__rmul__(self, other) y * x
__imul__(self, other) x *= y
Математическое умножение (например векторное)
__matmul__(self, other) x @ y
__rmatmul__(self, other) y @ x
__imatmul__(self, other) x @= y
Деление
__truediv__(self, other) x / y
__rtruediv__(self, other) y / x
__itruediv__(self, other) x /= y
Целочисленное деление
__floordiv__(self, other) x // y
__rfloordiv__(self, other) y // x
__ifloordiv__(self, other) x //= y
__divmod__(self, other) divmod(x, y)
Остаток
__mod__(self, other) x % y
__rmod__(self, other) y % x
__imod__(self, other) x %= y
Возведение в степень
__pow__(self, other) x ** y
__rpow__(self, other) y ** x
__ipow__(self, other) x **= y
Отрицание, модуль
__pos__(self) +x
__neg__(self) -x
__abs__(self) abs(x)
__len__(self) len(x)
Преобразование к стандартным типам
__int__(self) int(x)
__float__(self) float(x)
__complex__(self) complex(x)
__str__(self) str(x)
__round__(self, digits = 0) round(x, digits)
Блок with
__enter__(self)
__exit__(self)

Задачи:

Задача 1:

Реализуйте свой класс Complex для комплексных чисел, аналогично встроенной реализации complex:

  1. Добавьте инициализатор класса
  2. Реализуйте основные математические операции
  3. Реализуйте операцию модуля (abs, вызываемую как |c|)
  4. Оба класса должны давать осмысленный вывод как при print, так и просто при вызове в ячейке

Задача 2:

  1. Создайте класс Vector с полями x, y, z определите для него конструктор, метод __str__, необходимые арифметические операции. Реализуйте конструктор, который принимает строку в формате «x,y».
  2. Программа получает на вход число N, далее координаты N точек. Доопределите в классе Vector недостающие операторы, найдите и выведите координаты точки, наиболее удаленной от начала координат.
  3. Используя класс Vector выведите координаты центра масс данного множества точек.
  4. Даны два вектора. Выведите площадь параллелограмма, построенного на заданных векторах.
  5. Даны три вектора. Выведите объём параллелепипеда, построенного на заданных векторах.
  6. Среди данных точек найдите три точки, образующие треугольник с наибольшим периметром. Выведите данный периметр.
  7. Среди данных точек найдите три точки, образующие треугольник с наибольшей площадью. Выведите данную площадь.

Сайт построен с использованием Pelican. За основу оформления взята тема от Smashing Magazine. Исходные тексты программ, приведённые на этом сайте, распространяются под лицензией GPLv3, все остальные материалы сайта распространяются под лицензией CC-BY.

Использование метода .__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 ищет специальные методы в классах
  • Шаблон проектирования Фабрика и его реализация

Как получить класс, который вызвал метод другого класса?

As per Sergey Gornostaev, лучше передавать в метод класса информацию о вызывающем его другом классе в явном виде.

21 фев 2019 в 10:25

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

Лучше бы передавать в конструктор Logger информацию о вызывающем классе. Но можно и организовать разного рода костыли на основе анализа стека вызовов:

import inspect def get_caller_name(): frames = inspect.getouterframes(inspect.currentframe(), 3) if len(frames) > 1: info = frames[1] args, _, _, values = inspect.getargvalues(info.frame) obj = values.get('self') if obj is not None: return '<>.<>'.format(obj.__class__.__name__, info.function) return info.function 

Отслеживать
ответ дан 21 фев 2019 в 9:13
Sergey Gornostaev Sergey Gornostaev
66.5k 6 6 золотых знаков 54 54 серебряных знака 113 113 бронзовых знаков

О! Как раз подобную чорную магия я подозревал и опасался ) Не то, что я бы порекомендовал использовать — но респект!

21 фев 2019 в 10:47

Краткий ответ: никак

Развернутый ответ

Смотрим на сигнатуру Logger.log :

class Logger: . def log(self, log): # Этот класс должен перехватить имя класса, который его вызовет with open(self.name, "a+") as log_file: log_file.write(log) return log 

И мы видим, что все ООП — это единственный self , который будет указывать на экземпляр Logger .

Указания на caller — нет, и соотственно .. ответ: никак.

Чорная магия

Если есть желание почувствовать себя могучим магом и чародеем и навсегда прославиться среди коллег — можно, конечно, попробовать что-нибудь покопать с sys.settrace , манипуляцией с фреймами, кастомизировать неймспейсы globals / locals , черт побери — дизасм и копаться в байткоде.

Но: я никакого простого рецепта для такой магии не знаю и не рекомендую (кроме как для повышения квалификации).

Инициализация и протоколы — 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 студентов

Наши выпускники работают в компаниях:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *