Simple, yet Powerful : Python generic property auto assignment
Folks transitioning from java or any object-oriented language to python might struggling or spending their substantial time when assigning the method argument variables to their class level variable every-time they write reusable classes and functions definitions. For example,
class SomeModel(object): def __init__(self, arg1, arg2, arg3): self.arg1 = arg1 self.arg2 = arg2 self.arg3 = arg3
Imagine the above function, what if I have to long list of arguments and in multiple functions and methods? All the arguments need to be assigned manually one by one to class level variable.
Yes … I hear you. It’s time consuming and repetition of code. And every-time writing same type of code, always reminds us the eclipse auto generator or the Lombok library or annotation based. Unfortunately, there is no such library I could found readymade for python.
No worries, I got a better solution to get rid of this. Let’s take a look.
First, we will write a decorator which will look for the any kind and any type of argument in the function and set the class level properties and secondly, we will apply the decorator on the intended functions.
So when we talk about the arguments, the first thing comes into mind is , what kind of argument is it? To answer to it would be following,
- Default valued
- Positional
- varargs i.e variable arguments
Hence, we have to deal with these 3 kinds of arguments in out decorator to get maximum benefit out there.
- Handling default valued argument
if defaults: for attr, val in zip(reversed(attrs), reversed(defaults)): if verify(attr): setattr(self, attr, val)
- Handling positional arguments
positional_attrs = attrs[1:] for attr, val in zip(positional_attrs, args): if verify(attr): setattr(self, attr, val)
- Handling varargs
if varargs: remaining_args = args[len(positional_attrs):] if verify(varargs): setattr(self, varargs, remaining_args)
- Handling varkw
if kwargs: for attr, val in kwargs.items(): if verify(attr): setattr(self, attr, val)
Now lets wrap the above different handlers into a wrapper function .
@functools.wraps(func) def wrapper(self, *args, **kwargs): # handle default values if defaults: for attr, val in zip(reversed(attrs), reversed(defaults)): if verify(attr): setattr(self, attr, val) # handle positional arguments positional_attrs = attrs[1:] for attr, val in zip(positional_attrs, args): if verify(attr): setattr(self, attr, val) # handle varargs if varargs: remaining_args = args[len(positional_attrs):] if verify(varargs): setattr(self, varargs, remaining_args) # handle varkw if kwargs: for attr, val in kwargs.items(): if verify(attr): setattr(self, attr, val) return func(self, *args, **kwargs)
Note: To convert a regular function to wrapper, it has to be annotated with the wrap function.
Now let’s define our verify function that we have used in the above wrapper. The verify function is nothing but it determines if the argument is to be included in the auto assignment or not.
def verify(attr): if kwargs and attr in kwargs['exclude']: return False if not include or attr in include: return True else: return False
Now we have to write a function which can return the above wrapper instance.
def _autoset(func): attrs, varargs, varkw, defaults = inspect.getargspec(func) def verify(attr): if kwargs and attr in kwargs['exclude']: return False if not include or attr in include: return True else: return False @functools.wraps(func) def wrapper(self, *args, **kwargs): # handle default values if defaults: ................... ................... # handle positional arguments positional_attrs = attrs[1:] for attr, val in zip(positional_attrs, args): ................... ................... # handle varargs if varargs: ................... ................... # handle varkw if kwargs: ................... ................... return func(self, *args, **kwargs) return wrapper
There should be a function which accepts the list of arguments and pass through the wrapper to make these auto assign. Let’s write it.
def autoargs(*include, **kwargs): def _autoargs(func): attrs, varargs, varkw, defaults = inspect.getargspec(func) def verify(attr): if kwargs and attr in kwargs['exclude']: return False if not include or attr in include: return True else: return False @functools.wraps(func) def wrapper(self, *args, **kwargs): # handle default values if defaults: for attr, val in zip(reversed(attrs), reversed(defaults)): if verify(attr): setattr(self, attr, val) # handle positional arguments positional_attrs = attrs[1:] for attr, val in zip(positional_attrs, args): if verify(attr): setattr(self, attr, val) # handle varargs if varargs: remaining_args = args[len(positional_attrs):] if verify(varargs): setattr(self, varargs, remaining_args) # handle varkw if kwargs: for attr, val in kwargs.items(): if verify(attr): setattr(self, attr, val) return func(self, *args, **kwargs) return wrapper return _autoargs
To make it work , we have to import some of the required packages like inspect and functools.
Now we have the complete decorator for argument auto-assign at the instantiation. Let’s name it as common_utils.
Ok, now how to use the decorator ?
Sure… let’s take the example of the one that we have used starting of this article …
class SomeModel(object): def __init__(self, arg1, arg2, arg3): self.arg1 = arg1 self.arg2 = arg2 self.arg3 = arg3
After using the decorator, the code looks like bellow …
import common_utils as cu class SomeModel(object): @cu.autoargs() def __init__(self, arg1, arg2, arg3): pass
Hope you have enjoyed the read. Keep learning ……………