__new__ и __init__ в Python

Я изучаю Python и до сих пор могу рассказать о вещах ниже __new__ и __init__:

  • __new__ предназначен для создания объекта.
  • __init__ предназначен для инициализации объекта
  • __new__ вызывается перед __init__, поскольку __new__ возвращает новый экземпляр и __init__, вызываемый впоследствии для инициализации внутреннего состояния.
  • __new__ хорош для неизменяемого объекта, поскольку они не могут быть изменены после их назначения. Таким образом, мы можем вернуть новый экземпляр с новым состоянием.
  • Мы можем использовать __new__ и __init__ для изменяемого объекта, поскольку его внутреннее состояние можно изменить.

Но у меня есть еще вопросы.

  • Когда я создаю новый экземпляр, например a = MyClass("hello","world"), как эти аргументы передаются? Я имею в виду, как я должен структурировать класс, используя __init__ и __new__, поскольку они разные, и оба принимают произвольные аргументы, кроме первого аргумента по умолчанию.
  • self ключевое слово в терминах имени может быть изменено на что-то еще? Но мне интересно, что cls в терминах имени может быть изменен на что-то еще, поскольку это просто имя параметра?

Я сделал несколько экспериментов как таковые ниже:

>>> class MyClass(tuple):
    def __new__(tuple):
        return [1,2,3]

и я сделал следующее:

>>> a = MyClass()
>>> a
[1, 2, 3]

Хотя я сказал, что хочу вернуть tuple, этот код отлично работает и вернул мне [1,2,3]. Я знал, что мы передавали первые параметры в качестве типа, который мы хотели получить, после вызова функции __new__. Мы говорим о функции New правильно? Я не знаю, как другие языки возвращают тип, отличный от связанного типа?

И я тоже делал пытки:

>>> issubclass(MyClass,list)
False
>>> issubclass(MyClass,tuple)
True
>>> isinstance(a,MyClass)
False
>>> isinstance(a,tuple)
False
>>> isinstance(a,list)
True

Я больше не экспериментировал, потому что дальше не было яркого, и я решил остановиться там и решил спросить StackOverflow.

Сообщения SO, которые я прочитал:

Ответ 1

как я должен структурировать класс, используя __init__ и __new__, поскольку они разные, и оба принимают произвольные аргументы, кроме первого аргумента по умолчанию.

Редко вам придется беспокоиться о __new__. Обычно вы просто определяете __init__ и позволяете по умолчанию __new__ передавать ему аргументы конструктора.

self ключевое слово в терминах имени может быть изменено на что-то еще? Но мне интересно, что cls в терминах имени может быть изменено на что-то еще, поскольку это просто имя параметра?

Оба являются именами параметров, которые не имеют особого значения в языке. Но их использование - очень сильное соглашение в сообществе Python; большинство Pythonistas никогда не изменят имена self и cls в этих контекстах и ​​будут смущены, если кто-то еще сделает.

Обратите внимание, что использование def __new__(tuple) повторно связывает имя tuple внутри функции конструктора. Когда вы выполняете __new__, вы захотите сделать это как

def __new__(cls, *args, **kwargs):
    # do allocation to get an object, say, obj
    return obj

Хотя я сказал, что хочу вернуть tuple, этот код отлично работает и вернул мне [1,2,3].

MyClass() будет иметь значение, возвращаемое __new__. Там нет неявной проверки типов в Python; это ответственность программиста вернуть правильный тип ( "мы все соглашающиеся взрослые здесь). Возможность вернуть другой тип, чем запрошено, может быть полезна для реализации фабрик: вы можете вернуть подкласс запрашиваемого типа.

Это также объясняет поведение issubclass/isinstance, которое вы наблюдаете: отношение подкласса следует из вашего использования class MyClass(tuple), isinstance отражает то, что вы возвращаете "неправильный" тип из __new__.

Для справки ознакомьтесь с требованиями для __new__ в справочнике по языку Python.

Изменить: ok, вот пример потенциально полезного использования __new__. Класс Eel отслеживает, сколько угрей жив в процессе и отказывается выделять, если это превышает некоторый максимум.

class Eel(object):
    MAX_EELS = 20
    n_eels = 0

    def __new__(cls, *args, **kwargs):
        if cls.n_eels == cls.MAX_EELS:
            raise HovercraftFull()

        obj = super(Eel, cls).__new__(cls)
        cls.n_eels += 1
        return obj

    def __init__(self, voltage):
        self.voltage = voltage

    def __del__(self):
        type(self).n_eels -= 1

    def electric(self):
        """Is this an electric eel?"""
        return self.voltage > 0

Имейте в виду, что более интеллектуальные способы выполняют это поведение.