Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing inplace operations for methods in a class

In pandas lot's of methods have the keyword argument inplace. This means if inplace=True, the called function will be performed on the object itself, and returns None, on the other hand if inplace=False the original object will be untouched, and the method is performed on the returned new instance. I've managed to implement this functionality as follows:

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

It works as expected. However I have many methods where I repeat that same template:

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

To avoid repetition, I would like to create a decorator and mark functions which can be executed inplace and also non-inplace. I would like to use it this way

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

and I expect it to behave as the same as the example ahove. I've tried writing different decorators

(something starting like this

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

)

but I got stuck every time. I need the reference for self in order to return a copy of the class, which I don't have there. Also it feels a little vague to change the function signature with a decorator. I have several questions: Is this behaviour can be implemented? Do I need a class decorator? Is it considered to be a bad practice, and if so, what would be the best option to deal with such issue?

like image 352
Péter Leéh Avatar asked Aug 01 '20 16:08

Péter Leéh


People also ask

How do you call a function inside a class in Python?

Properties of First-Class functions Create a program to understand Python functions as an object. def MyObject(text): # Pass an argument. # Call the function inside the print() function. # call the function using the str variable.

What is __ class __ in Python?

__class__ is an attribute on the object that refers to the class from which the object was created. a. __class__ # Output: <class 'int'> b. __class__ # Output: <class 'float'> After simple data types, let's now understand the type function and __class__ attribute with the help of a user-defined class, Human .

What is __ INT __ in Python?

The __int__ method is called to implement the built-in int function. The __index__ method implements type conversion to an int when the object is used in a slice expression and the built-in hex , oct , and bin functions.

What is __ call __ Python?

__call__() A callable object is one which can be called like a function. In Python, __call__() is used to resolve the code associated with a callable object. Any object can be converted to a callable object just by writing it in a function call format.


1 Answers

If your method has return self, the following works:

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

I tested it:

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

And got the expected result:

5
9
9 12

Hope this works for your needs.

like image 52
Green05 Avatar answered Sep 27 '22 16:09

Green05