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
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))
I know there are few missing things: no type hints, no documentation for internal methods. Any criticism/improvement is appreciated.