Bhubaneswar, Odisha, India
+91-8328865778
support@softchief.com

Simple, yet Powerful : Python generic property auto assignment

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 ……………