Skip to main content
Became Hot Network Question
Tweeted twitter.com/StackCodeReview/status/1303030270749179906
added 7 characters in body
Source Link
class Dummy:
    def __init__(self, x):
        self.x = x

    @inplacify
    def increment(self, value):
        self.x += value
        return self

    @inplacify
    def multiply(self, value):
        """
        This is some numpy-style doc.

        Parameters
        ----------
        value : float
            The value ``x`` will be multiplied with it.

        Returns
        -------
        None..
        Just another section to see if it's working.
        """
        self.x *= value
        return self
    
    @inplacify
    def subtract(self, value):
        """
        This is some numpy-style doc. Without any following
        sections after Parameters.

        Parameters
        ----------
        value : float
            The value to subtract from ``x``.
        """
        self.x -= value
        return self
    
if __name__ == "__main__":
    a = Dummy(1)
    a.increment(1)
    assert a.x == 2
    b = a.increment(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.increment))


    a.multiply(1)
    assert a.x == 2
    b = a.multiply(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.multiply))
    
    a.subtract(1)
    assert a.x == 1
    b = a.subtract(2, inplace=False)
    assert a.x == 1
    assert b.x == -1
    print(help(a.subtract))
class Dummy:
    def __init__(self, x):
        self.x = x

    @inplacify
    def increment(self, value):
        self.x += value
        return self

    @inplacify
    def multiply(self, value):
        """
        This is some numpy-style doc.

        Parameters
        ----------
        value : float
            The value ``x`` will be multiplied with it.

        Returns
        -------
        None..
        Just another section to see if it's working.
        """
        self.x *= value
        return self
    
    @inplacify
    def subtract(self, value):
        """
        This is some numpy-style doc. Without any following
        sections.

        Parameters
        ----------
        value : float
            The value to subtract from ``x``.
        """
        self.x -= value
        return self
    
if __name__ == "__main__":
    a = Dummy(1)
    a.increment(1)
    assert a.x == 2
    b = a.increment(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.increment))


    a.multiply(1)
    assert a.x == 2
    b = a.multiply(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.multiply))
    
    a.subtract(1)
    assert a.x == 1
    b = a.subtract(2, inplace=False)
    assert a.x == 1
    assert b.x == -1
    print(help(a.subtract))
class Dummy:
    def __init__(self, x):
        self.x = x

    @inplacify
    def increment(self, value):
        self.x += value
        return self

    @inplacify
    def multiply(self, value):
        """
        This is some numpy-style doc.

        Parameters
        ----------
        value : float
            The value ``x`` will be multiplied with it.

        Returns
        -------
        None..
        Just another section to see if it's working.
        """
        self.x *= value
        return self
    
    @inplacify
    def subtract(self, value):
        """
        This is some numpy-style doc. Without any
        sections after Parameters.

        Parameters
        ----------
        value : float
            The value to subtract from ``x``.
        """
        self.x -= value
        return self
    
if __name__ == "__main__":
    a = Dummy(1)
    a.increment(1)
    assert a.x == 2
    b = a.increment(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.increment))


    a.multiply(1)
    assert a.x == 2
    b = a.multiply(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.multiply))
    
    a.subtract(1)
    assert a.x == 1
    b = a.subtract(2, inplace=False)
    assert a.x == 1
    assert b.x == -1
    print(help(a.subtract))
Source Link

Inplace operations for a class with numpy-style docs

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 implemented it in my projects too, but I ended up with too much repetitive coding, so I decided to make a decorator which gets the job done. It also appends to the method's docstring, if it's formatting follows numpy-style docs. The only thing that needs to be added is return self to every method's end. I think that step also can be skipped with metaclasses, but for the time being it's not implemented.

import re
from functools import wraps
from copy import copy


_inplace_doc = """\n\tinplace : bool, optional
            Whether to apply the operation on the dataset in an "inplace" manner.
            This means if inplace is True it will apply the changes directly on
            the current object and returns None. If inplace is False, it will
            leave the current object untouched, but returns a copy of it, and
            the operation will be performed on the copy. It's useful when
            chaining operations on a dataset. See fluent interfacing and
            method cascading.\n\n\t"""


def _has_parameter_section(method):
    try:
        return "Parameters" in method.__doc__
    except TypeError:
        return False  # There's no docstring provided


def _build_doc(method, appendix):

    # finding sections (single words above 4+ slashes)
    patt = r"(\w+(?=\s*[-]{4,}[^/]))"

    splitted_doc = re.split(patt, method.__doc__)

    try:
        # We try to append to section below Parameters.
        target = splitted_doc.index("Parameters") + 1
    except ValueError:
        return method.__doc__

    splitted_doc[target] = splitted_doc[target].rstrip() + appendix

    return ''.join(_ for _ in splitted_doc if _ is not None)
   

def _update_doc(method, doc):
    if _has_parameter_section(method):
        newdoc = _build_doc(method, doc)
        method.__doc__ = newdoc
    else:
        newdoc = """\n\tParameters
        ----------""" + _inplace_doc

        nodoc_head = (f"Docstring automatically created for {method.__name__}. "
                      "Parameter list may not be complete.\n")
        if method.__doc__ is not None:
            method.__doc__ += newdoc
        else:
            method.__doc__ = nodoc_head + newdoc


def inplacify(method):
    """
    Decorator used to allow a function in a class to be called
    as `inplace`.
    """
    _update_doc(method, _inplace_doc)

    @wraps(method)
    def wrapper(self, *args, **kwds):
        inplace = kwds.pop("inplace", True)
        if inplace:
            method(self, *args, **kwds)
        else:
            return method(copy(self), *args, **kwds)
    return wrapper

Test cases:

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

    @inplacify
    def increment(self, value):
        self.x += value
        return self

    @inplacify
    def multiply(self, value):
        """
        This is some numpy-style doc.

        Parameters
        ----------
        value : float
            The value ``x`` will be multiplied with it.

        Returns
        -------
        None..
        Just another section to see if it's working.
        """
        self.x *= value
        return self
    
    @inplacify
    def subtract(self, value):
        """
        This is some numpy-style doc. Without any following
        sections.

        Parameters
        ----------
        value : float
            The value to subtract from ``x``.
        """
        self.x -= value
        return self
    
if __name__ == "__main__":
    a = Dummy(1)
    a.increment(1)
    assert a.x == 2
    b = a.increment(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.increment))


    a.multiply(1)
    assert a.x == 2
    b = a.multiply(2, inplace=False)
    assert a.x == 2
    assert b.x == 4
    print(help(a.multiply))
    
    a.subtract(1)
    assert a.x == 1
    b = a.subtract(2, inplace=False)
    assert a.x == 1
    assert b.x == -1
    print(help(a.subtract))

I know there are few missing things: no type hints, no documentation for internal methods. Any criticism/improvement is appreciated.