Реализация операций на месте для методов в классе - PullRequest
5 голосов
/ 01 августа 2020

В pandas многие методы имеют аргумент ключевого слова inplace. Это означает, что если inplace=True, вызываемая функция будет выполнена для самого объекта и вернет None, с другой стороны, если inplace=False исходный объект останется нетронутым, и метод будет выполнен на возвращенном новом экземпляре. Мне удалось реализовать эту функциональность следующим образом:

from copy import copy

class Dummy:
    def __init__(self, x: int):
        self.x = x

    def increment_by(self, increment: int, inplace=True):
        if inplace:
            self.x += increment
        else:
            obj = copy(self)
            obj.increment_by(increment=increment, inplace=True)
            return obj

    def __copy__(self):
        cls = self.__class__
        klass = cls.__new__(cls)
        klass.__dict__.update(self.__dict__)
        return klass
    
if __name__ == "__main__":
    a = Dummy(1)
    a.increment_by(1)
    assert a.x == 2
    b = a.increment_by(2, inplace=False)
    assert a.x == 2
    assert b.x == 4

Работает, как ожидалось. Однако у меня есть много методов, в которых я повторяю тот же шаблон:

def function(self, inplace=True, **kwds)
    if inplace:
        # do something
    else:
        obj = copy(self)
        obj.function(inplace=True, *args, **kwds)
        return obj

Чтобы избежать повторения, я хотел бы создать декоратор и отметить функции, которые могут выполняться на месте, а также не на месте. Я хотел бы использовать его таким образом

from copy import copy

class Dummy:
    def __init__(self, x: int):
        self.x = x

    @inplacify
    def increment_by(self, increment: int):
        self.x += increment # just the regular inplace way

    def __copy__(self):
        cls = self.__class__
        klass = cls.__new__(cls)
        klass.__dict__.update(self.__dict__)
        return klass

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

(что-то вроде этого

def inplacify(method):
    def inner(self, *method_args, **method_kwds):
        inplace = method_kwds.pop("inplace", True)
        def new_method(inplace, *method_args, **method_kwds):

)

, но каждый раз застревал. Мне нужна ссылка на self, чтобы вернуть копию класса, которого у меня там нет. Также немного расплывчато изменить сигнатуру функции с помощью декоратора. У меня несколько вопросов: можно ли реализовать такое поведение? Нужен ли мне декоратор класса? Считается ли это плохой практикой, и если да, то что было бы лучшим вариантом для решения такой проблемы?

1 Ответ

3 голосов
/ 01 августа 2020

Если в вашем методе есть return self, работает следующее:

import copy

def inplacify(method):
    def wrap(self,*a,**k):
        inplace = k.pop("inplace",True)
        if inplace:
            method(self,*a,**k)
        else:
            return method(copy.copy(self),*a,**k)
    return wrap

class classy:
    def __init__(self,n):
        self.n=n

    @inplacify
    def func(self,val):
        self.n+=val
        return self

Я его тестировал:

inst = classy(5)
print(inst.n)
inst.func(4)
print(inst.n)
obj = inst.func(3,inplace=False)
print(inst.n,obj.n)

И получил ожидаемый результат:

5
9
9 12

Надеюсь, это сработает для ваших нужд.

...