Невозможно выполнить глубокую копирование класса, в котором определены и __init__, и __new__ - PullRequest
1 голос
/ 16 января 2020

У меня (что мне кажется) немного странная проблема. Я определил класс с init и новым , определенным ниже:

class Test:

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

    def __new__(cls, *args, **kwargs):
        new_inst = object.__new__(cls)
        new_inst.__init__(*args, **kwargs)
        new_inst.extra = 2
        return new_inst

Если использовать его в обычном режиме, это прекрасно работает:

test = Test(1)
assert test.extra == 2

Однако, он не будет копировать .deepcopy:

import copy
copy.deepcopy(test)

дает

TypeError: __init__() missing 1 required positional argument: 'num1'

Это может быть связано с Украшение класса с помощью оболочки класса и __new __ - Я не могу понять, как именно, но я пытаюсь сделать то же самое здесь - мне нужно new , чтобы применить обертку класса к созданному мной экземпляру Test.

Любая помощь с благодарностью получил!

Ответы [ 2 ]

2 голосов
/ 16 января 2020

Технически это не проблема для вызова __init__ из __new__, но это избыточно, так как вызов __init__ происходит автоматически, когда __new__ возвращает экземпляр.


Теперь прибывает в почему deepcopy терпит неудачу, мы можем посмотреть его внутренности немного.

Когда __deepcopy__ не определен в классе, он попадает в это условие:

reductor = getattr(x, "__reduce_ex__", None)
rv = reductor(4)

Теперь здесь reductor(4) возвращает функцию , которая будет использоваться для воссоздания объекта , тип объекта (Test), передаваемые аргументы и его состояние (в данном случае элементы в словаре экземпляра test.__dict__):

>>> !rv
(
    <function __newobj__ at 0x7f491938f1e0>,  # func
    (<class '__main__.Test'>,),  # type + args in a single tuple
    {'num1': 1, 'extra': []}, None, None) # state

Теперь он вызывает _reconstruct с этими данными:

def _reconstruct(x, memo, func, args,
                 state=None, listiter=None, dictiter=None,
                 deepcopy=deepcopy):
    deep = memo is not None
    if deep and args:
        args = (deepcopy(arg, memo) for arg in args)
    y = func(*args)
    ...

Здесь этот вызов закончится Вызов:

def __newobj__(cls, *args):
    return cls.__new__(cls, *args)

Но поскольку args пусто, а cls <class '__main__.Test'>, вы получите ошибку.


Теперь, как Python определяет эти аргументы для вашего объект, как кажется, это проблема?

Для этого нам нужно рассмотреть: reductor(4), где редуктор равен __reduce_ex__, а 4, переданный здесь, является pickle версия протокола.

Теперь этот __reduce_ex__ внутренне вызывает reduce_newobj, чтобы получить функцию создания объекта, аргументы, состояние и т. д. c для создаваемой новой копии.

Аргументы сами по себе определяются с помощью _PyObject_GetNewArguments.

Теперь эта функция ищет в классе __getnewargs_ex__ или __getnewargs__, поскольку наш класс не имеет это, мы ничего не получаем за аргументы.


Теперь давайте добавим этот метод и попробуем еще раз:

import copy


class Test:

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

    def __getnewargs__(self):
        return ('Eggs',)

    def __new__(cls, *args, **kwargs):
        print(args)
        new_inst = object.__new__(cls)
        new_inst.__init__(*args, **kwargs)
        new_inst.extra = []
        return new_inst

test = Test([])

xx = copy.deepcopy(test)

print(xx.num1, test.num1, id(xx.num1), id(test.num1))

# ([],)
# ('Eggs',)
# [] [] 139725263987016 139725265534088

Удивительно, но для глубокой копии xx не хранится Eggs в num1 хотя мы возвращаем его с __getnewargs__. Это связано с тем, что функция _reconstruct повторно добавляет глубинную копию состояния, которое оно первоначально получило, к экземпляру после его создания, что отменяет эти изменения.


def _reconstruct(x, memo, func, args,
                 state=None, listiter=None, dictiter=None,
                 deepcopy=deepcopy):
    deep = memo is not None
    if deep and args:
        args = (deepcopy(arg, memo) for arg in args)
    y = func(*args)
    if deep:
        memo[id(x)] = y

    if state is not None:
        ...
            if state is not None:
                y.__dict__.update(state)  <---
    ...

Есть ли другие способы сделать это? это?

Обратите внимание на приведенное выше объяснение, и рабочая функция предназначена только для объяснения проблемы. Я бы на самом деле не назвал это лучшим или худшим способом сделать это.

Да, вы могли бы определить свой собственный __deepcopy__ крючок класса для дальнейшего управления поведением. Я бы оставил это упражнение пользователю.

0 голосов
/ 16 января 2020

ОК - это потому, что я делаю это неправильно - я не должен явно вызывать init из new . Culpe mea.

...