Welcome, guest | Sign In | My Account | Store | Cart

In most of the object oriented codes we write, we need to set class attributes to the given argument values and this is a very line-consuming thing. To get over these redundant lines, I found a solution using decorators.

Python, 30 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class RedundantTest:
    def __init__(self, x, y, z, t):
        # Here are the redundant lines:
        self.x = x
        self.y = y
        self.z = z
        self.t = t
        print x,y,z,t

# ===================================

def injectArguments(inFunction):
    def outFunction(*args,**kwargs):
        _self = args[0]
        _self.__dict__.update(kwargs)
        inFunction(*args,**kwargs)
    return outFunction
 
class Test:
    @injectArguments
    def __init__(self, x, y, z, t):
        # We don't have to set each attribute. They're already set by injectArguments
        print self.x,self.y,self.z,self.t
 
    @injectArguments
    def function(self, name):
	print "Name:",self.name
 
t = Test(x=4, y=5, z=6, t=7)
t.function(name="Emre")

args dictionary contains the arguments and kwargs dictionary contains the keyword arguments. arg[0] corresponds to self argument. So what we do is we update self.__dict__ with {'x':4, 'y':5,'z':6,'t':7} where self corresponds to the Test instance. This way, bulky self.x = x, etc. codes are eliminated.

I should note that keyword arguments (x=4, y=5, ... ) should be used to use this decorator.

I don't know if any built-it decorator like this exists in Python libraries but it seems like I'll be using this a lot.

8 comments

Filippo Squillace 13 years, 6 months ago  # | flag

This solution is a variant that allow to pass parameter not only with kwargs notation.

def injectArguments(inFunction):
    """
    This function allows to reduce code for initialization of parameters of a method through the @-notation
    You need to call this function before the method in this way: @injectArguments
    """
    def outFunction(*args, **kwargs):
        _self = args[0]
        _self.__dict__.update(kwargs)
        # Get all of argument's names of the inFunction
        _total_names = inFunction.func_code.co_varnames[1:inFunction.func_code.co_argcount]
        # Get all of the values
        _values = args[1:]
        # Get only the names that don't belong to kwargs
        _names = [n for n in _total_names if not kwargs.has_key(n)]

        # Match names with values and update __dict__
        d={}
        for n, v in zip(_names,_values):
            d[n] = v
        _self.__dict__.update(d)
        inFunction(*args,**kwargs)

    return outFunction


class Test:
""""""
@injectArguments
def __init__(self, name, surname):
    pass


if __name__=='__main__':
    t = Test('mickey', surname='mouse')
    print t.name, t.surname
Ahmet Emre Aladağ (author) 13 years, 6 months ago  # | flag

Wow! That's what I've been looking for. It's been really useful to learn func_code.co_varnames. Thank you very much. This way, this snippet has became really useful.

Martin Miller 13 years, 6 months ago  # | flag

In Python 2.4+ the code snippet below from @Filippo Squillace's version:

# Match names with values and update __dict__
d={}
for n, v in zip(_names,_values):
    d[n] = v
_self.__dict__.update(d)

could be replaced with simply:

# Match argument names with values and update __dict__
_self.__dict__.update(zip(_names,_values))

Regardless, both the original and modified recipe's seem elegant and useful.

Martin Miller 13 years, 5 months ago  # | flag

Both the OP's and Filippo Squillace's variant should end their nested outFunction defintion by returning the result of calling the method being decorated, otherwise it will default to None. In other words, like this:

def injectArguments(inFunction):
    def outFunction(*args, **kwargs):
            ...
        return inFunction(*args, **kwargs) # return result of calling the decorated function

return outFunction
zzortell 7 years, 8 months ago  # | flag

Adapted for python3:

```py

!/usr/bin/env python3

def injectArguments(inFunction): """ Decorator injecting arguments of a method as attributes

Found here: http://code.activestate.com/recipes/577382-keyword-argument-injection-with-python-decorators/

"""

def outFunction(*args, **kwargs):
    _self = args[0]
    _self.__dict__.update(kwargs)
    # Get all of argument's names of the inFunction
    _total_names = inFunction.__code__.co_varnames[1:inFunction.__code__.co_argcount]
    # Get all of the values
    _values = args[1:]
    # Get only the names that don't belong to kwargs
    _names = [n for n in _total_names if not n in kwargs]

    # Match argument names with values and update __dict__
    _self.__dict__.update(zip(_names,_values))

    return inFunction(*args,**kwargs)

return outFunction

if __name__=='__main__': class Test: @injectArguments def __init__(self, name, surname): pass

t = Test('mickey', surname='mouse')
assert(t.name == 'mickey' and t.surname == 'mouse')

```

Thank you!

zzortell 7 years, 8 months ago  # | flag

Adapted for python3:

#!/usr/bin/env python3


def injectArguments(inFunction):
    """
    Decorator injecting arguments of a method as attributes

    Found here: http://code.activestate.com/recipes/577382-keyword-argument-injection-with-python-decorators/

    """

    def outFunction(*args, **kwargs):
        _self = args[0]
        _self.__dict__.update(kwargs)
        # Get all of argument's names of the inFunction
        _total_names = inFunction.__code__.co_varnames[1:inFunction.__code__.co_argcount]
        # Get all of the values
        _values = args[1:]
        # Get only the names that don't belong to kwargs
        _names = [n for n in _total_names if not n in kwargs]

        # Match argument names with values and update __dict__
        _self.__dict__.update(zip(_names,_values))

        return inFunction(*args,**kwargs)

    return outFunction


if __name__=='__main__':
    class Test:
        @injectArguments
        def __init__(self, name, surname):
            pass

    t = Test('mickey', surname='mouse')
    assert(t.name == 'mickey' and t.surname == 'mouse')

Thank you!

zzortell 7 years, 8 months ago  # | flag

Modified to accept default values of non-passed arguments:

#!/usr/bin/env python3
# -*-coding:Utf-8 -*


def injectArguments(inFunction):
    """
    Decorator injecting arguments of a method as attributes

    Found here: http://code.activestate.com/recipes/577382-keyword-argument-injection-with-python-decorators/

    """

    def outFunction(*args, **kwargs):
        _self = args[0]
        _self.__dict__.update(kwargs)

        # Get all of argument's names of the inFunction
        _total_names = inFunction.__code__.co_varnames[1:inFunction.__code__.co_argcount]
        # Get all of the values
        _values = args[1:]
        # Get only the names that don't belong to kwargs
        _names = [n for n in _total_names if not n in kwargs]

        # Match argument names with values and update __dict__
        _self.__dict__.update(zip(_names,_values))

        # Add default value for non-specified arguments
        nb_defaults = len(_names) - len(_values)
        _self.__dict__.update(zip(_names[-nb_defaults:], inFunction.__defaults__[-nb_defaults:]))

        return inFunction(*args,**kwargs)

    return outFunction


if __name__=='__main__':
    class Test:
        @injectArguments
        def __init__(self, name, surname, default='lol'):
            pass

    t = Test('mickey', surname='mouse')
    assert(t.name == 'mickey' and t.surname == 'mouse' and t.default == 'lol')
zzortell 7 years, 4 months ago  # | flag

You can find a new debugged version here: https://gist.github.com/Zzortell/2403054294b6312a32033b747ec0ad2c

Created by Ahmet Emre Aladağ on Sun, 5 Sep 2010 (GPL3)
Python recipes (4591)
Ahmet Emre Aladağ's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks

  • Licensed under the GPL 3
  • Viewed 24005 times
  • Revision 2 (updated 13 years ago)