(AttributeError: __enter__) при использовании метода __new__ magi c - PullRequest
0 голосов
/ 18 февраля 2020

Я пытаюсь использовать метод __new__ для проверки правильности аргумента (str) перед его обработкой, поэтому я делаю следующее:

class foo:

    def __new__(cls, argument):
        if not isinstance(argument, str):
            raise TypeError('argument must be str')
        print("passed __new__")

    def __init__(self, argument):
        self.argument = argument
        print("passed __init__")

    def __enter__(self):
        print("passed __enter__")

    def __exit__(self, *args):
        print("passed __exit__")

затем, когда я вызываю это с помощью with ключевое слово, которое вызывает __enter__ и __exit__

with foo('test'):
    print("i am between __enter__ and __exit__, damn!")

Я получаю этот вывод:

passed __new__
AttributeError: __enter__

Я ожидал этот вывод:

passed __new__
passed __init__
passed __enter__
i am between __enter__ and __exit__, damn!
passed __exit__

I замечено при отбрасывании __new__ метода полностью; это будет работать. Почему использование __new__ нарушает это? и как мне заставить его работать даже с __new__ существующим?

1 Ответ

0 голосов
/ 18 февраля 2020

__new__ должен фактически создать и вернуть экземпляр вашего класса.

class foo:

    def __new__(cls, argument):
        if not isinstance(argument, str):
            raise TypeError('argument must be str')
        print("passed __new__")
        <b>return super().__new__(cls)</b>

    def __init__(self, argument):
        self.argument = argument
        print("passed __init__")

    def __enter__(self):
        print("passed __enter__")

    def __exit__(self, *args):
        print("passed __exit__")

В вашем исходном коде foo.__new__ вернул None, и действительно None не имеет атрибута __enter__. Кроме того, поскольку None не является экземпляром foo, неявный вызов foo.__init__ был подавлен.


Поскольку foo является экземпляром type, вызов foo('test') это сокращение от

type.__call__(foo, 'test')

Вы можете себе представить, что __call__ определяется как

def __call__(cls, *args, **kwargs):
    obj = cls.__new__(*args, **kwargs)
    if isinstance(obj, cls):
        obj.__init__(*args, **kwargs)
    return obj

Переопределение __new__ означает, что вы хотите изменить способ создания экземпляра . 1027 *, а переопределение __init__ означает, что вы хотите изменить способ, которым уже созданный экземпляр получает инициализированный . В этом случае вы вообще не будете создавать новый экземпляр, если аргумент имеет неправильный тип.

...