I'm writing a class that has a number of methods operating on similar types of arguments:
class TheClass():
def func1(self, data, params, interval):
....
def func2(self, data, params):
....
def func3(self, data, interval):
....
def func4(self, params):
....
...
There's a certain convention about these arguments (e.g. data
/params
should be numpy.farrays
, interval
- a list
of 2 floats
), but I'd like to allow user to have more freedom: e.g. functions should accept int
as data
or params
, or only one int
as an interval
and then assume that this is the end point with the starting point 0
, etc.
So to avoid all these transformations within methods, that should do only the logic, I'm using decorators like this:
def convertparameters(*types):
def wrapper(func):
def new_func(self, *args, **kwargs):
# Check if we got enough parameters
if len(types) > len(args):
raise Exception('Not enough parameters')
# Convert parameters
new_args = list(args)
for ind, tip in enumerate(types):
if tip == "data":
new_args[ind] = _convert_data(new_args[ind])
elif tip == "params":
new_args[ind] = _convert_params(new_args[ind])
elif tip == "interval":
new_args[ind] = _convert_interval(new_args[ind])
else:
raise Exception('Unknown type for parameter')
return func(self, *new_args, **kwargs)
return new_func
return wrapper
Where _convert_data
, _convert_params
and _convert_interval
do the dirty job. Then I define the class as follows:
class TheClass():
@convertparameters("data", "params", "interval")
def func1(self, data, params, interval):
....
@convertparameters("data", "params")
def func2(self, data, params):
....
@convertparameters("data", "interval")
def func3(self, data, interval):
....
@convertparameters("params")
def func4(self, params):
....
...
It does the trick, but there're several quite disturbing things:
@staticmethod
or something that post-process output of the method) the ordering of these decorators matterfunc1(*args, **kwargs)
Are there any better (or more "Pythonic") ways to do such massive parameter transformation?
UPDATE 1: SOLUTION based on n9code's suggestion
In order to avoid confusion there's a modification of the convertparameters
wrapper that solves 3rd issue (masking of signature and docstring of methods) - suggested by n9code for Python >2.5.
Using decorator
module (to be installed separately: pip install decorator
) we can transfer all function's "metadata" (docstring, name and signature) at the same time getting rid of nested structure inside the wrapper
from decorator import decorator
def convertparameters(*types):
@decorator
def wrapper(func, self, *args, **kwargs):
# Check if we got enough parameters
if len(types) > len(args):
raise Exception('Not enough parameters')
# Convert parameters
new_args = list(args)
for ind, tip in enumerate(types):
if tip == "data":
new_args[ind] = _convert_data(new_args[ind])
elif tip == "params":
new_args[ind] = _convert_params(new_args[ind])
elif tip == "interval":
new_args[ind] = _convert_interval(new_args[ind])
else:
raise Exception('Unknown type for parameter')
return func(self, *new_args, **kwargs)
return wrapper
UPDATE 2: MODIFIED SOLUTION based on zmbq's suggestion
Using inspect
module we could also get rid of arguments of decorator, checking the names of arguments of the initial function. This will eliminate another layer of the decorator
from decorator import decorator
import inspect
@decorator
def wrapper(func, self, *args, **kwargs):
specs = inspect.getargspec(func)
# Convert parameters
new_args = list(args)
for ind, name in enumerate(specs.args[1:]):
if name == "data":
new_args[ind] = _convert_data(new_args[ind])
elif name == "params":
new_args[ind] = _convert_params(new_args[ind])
elif name == "interval":
new_args[ind] = _convert_interval(new_args[ind])
return func(self, *new_args, **kwargs)
And the usage is much simpler then. The only important thing is keep using the same names for arguments between different functions.
class TheClass():
@convertparameters
def func1(self, data, params, interval):
....
@convertparameters
def func2(self, data, params):
....
Class methods don't need a class instance. They can't access the instance ( self ) but they have access to the class itself via cls . Static methods don't have access to cls or self . They work like regular functions but belong to the class's namespace.
__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 .
In python, unlike other languages, you cannot perform method overloading by using the same method name. Why? Everything is an object in python, classes, and even methods. Say you have an object Addition, which is a class (everything in python is an object, so the class Addition is an object too).
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.
This is a problem, you are right, but it is being solved easily with functools.wraps
. Simply decorate your newfunc
with it, and you will save the signature of the original function.
from functools import wraps
def convertparameters(*types):
def wrapper(func):
@wraps(func)
def new_func(self, *args, **kwargs):
pass # Your stuff
Tough, this works only for Python 3. In Python 2, the signature would not be preserved, only __name__
and __doc__
would be. So in case of Python, you could use the decorator
module:
from decorator import decorator
def convertparameters(*types):
@decorator
def wrapper(func, self, *args, **kwargs):
pass # return a result
return wrapper
EDIT based on user3160867's update.
To make the code a bit more concise, don't provide arguments to the decorator - deduce them from the decorated function.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With