1
\$\begingroup\$

The code creates a meta class and its implementation to set the values passed in as arguments to methods on a class automatically on the object avoiding a lot of self.a=a code at the start of methods.

>>> from functools import wraps
>>> import inspect
>>> 
>>> def setMethodAttrs(func):
...    @wraps(func)
...    def wrapper(*args, **kwargs):
...        formalArgs=inspect.getargspec(func)[0]
...        if len(args)>1:
...           for x in range(1, len(args)):
...              setattr(args[0], formalArgs[x], args[x])
...        for k, v in kwargs.iteritems():
...           setattr(vars()['args'][0], k, v)
...        return func(*args, **kwargs)
...    return wrapper
... 
>>> def classAttrs(cls):
...     for key, val in vars(cls).items():
...         if callable(val):
...             setattr(cls,key, setMethodAttrs(val))
...     return cls
...  
... 
>>> 
>>> @classAttrs
... class trythis:
...    def __init__(self):
...       self.firstvar=1
...       self.secondvar=2
...       self.thirdvar=3
...       self.fourthvar=4
...    def method1(self,firstvar=None,secondvar=None,thirdvar=None):
...       print("firstvar {}".format(self.firstvar))
...       print("secondvar {}".format(self.secondvar))
...       print("thirdvar {}".format(self.thirdvar))
...       print("fourthvar {}".format(self.fourthvar))
...    def method2(self, secondvar=None, fourthvar=None):
...       print("firstvar {}".format(self.firstvar))
...       print("secondvar {}".format(self.secondvar))
...       print("thirdvar {}".format(self.thirdvar))
...       print("fourthvar {}".format(self.fourthvar))
...   
...  
... 
>>> a=trythis()
>>> a.method1()
firstvar 1
secondvar 2
thirdvar 3
fourthvar 4
>>> a.method1(firstvar=9)
firstvar 9
secondvar 2
thirdvar 3
fourthvar 4
>>> a.method2(11)
firstvar 9
secondvar 11
thirdvar 3
fourthvar 4
>>> a.method1(19, thirdvar=21)
firstvar 19
secondvar 11
thirdvar 21
fourthvar 4
>>> a.method2(fourthvar=40)
firstvar 19
secondvar 11
thirdvar 21
fourthvar 40
>>> 
\$\endgroup\$
  • 1
    \$\begingroup\$ What is the purpose? Do you have a better example than this hypothetical class? \$\endgroup\$ – 301_Moved_Permanently Sep 3 '17 at 11:45
  • \$\begingroup\$ I had classes with methods with arguments which I set with if a: self.a=a for each argument. This code repeated for every argument. I wanted to remove the repetitive code so this was my solution. Now you call a method and the arguments are set on the object by the call \$\endgroup\$ – amadain Sep 3 '17 at 16:00
  • \$\begingroup\$ also if you define the constructor with arguments they are automatically set as object attributes (def __init__(self,firstvar,secondvar,thirdvar,fourthvar) means you dont have to put in self.firstvar=, etc) \$\endgroup\$ – amadain Sep 4 '17 at 12:17
2
\$\begingroup\$
...        if len(args)>1:
...           for x in range(1, len(args)):
...              setattr(args[0], formalArgs[x], args[x])

You’re pretty much re-inventing zip here, use that instead. To extract out the first (self) parameter, you can use next (in Python 3) or slicing (in Python 2). To get a consistent behaviour accross Python versions, you can use itertools.izip in Python 2.

Something along the lines of:

arguments = zip(formalArgs, args)
_, self = next(arguments)
for name, value in arguments:
    setattr(self, name, value)

You can also define your wrapper as def wrapper(self, *args, **kwargs) to remove the need for next and already extract the value of self out of the args tuple. This will mean you’ll have to slice formalArgs though:

def wrapper(self, *args, **kwargs):
    formal_args = inspect.getargspec(func)[0][1:]
    for name, value in zip(formal_args, args):
        setattr(self, name, value)

You also don't need to inspect func at each call as, since you're using a decorator, you’re pretty much guaranteed that this object will never change during the lifetime of the program. Besides, it may be more readable to use the name, rather than the index, of the attribute you’re trying to extract out of the nametuple (since Python 2.6).

Lastly, getargspec has been deprecated since Python 3.0, so you'd better off using getfullargspec. But this function doesn't exist in Python 2, so you can try a conditional import:

from itertools import chain
from functools import wraps
try:
    # Python 3
    from inspect import getfullargspec as getargspec
except ImportError:
    # Python 2
    from inspect import getargspec
    from itertools import izip as zip


def store_attributes(method):
   arguments_names = getargspec(method).args[1:]

   @wraps(method)
   def wrapper(self, *args, **kwargs):
       for name, value in chain(zip(arguments_names, args), kwargs.items()):
           setattr(self, name, value)
       return method(self, *args, **kwargs)

   return wrapper


def class_store_attributes(cls):
    for name, value in vars(cls).items():
        if callable(value):
            setattr(cls, name, store_attributes(value))
    return cls

Usage being the same, even though I still don't get why you would use @class_store_attributes on the whole class rather than @store_attributes on a per-method basis.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks. I have a lot of methods in the class I'm applying this to so I thought applying it to the class would be better \$\endgroup\$ – amadain Sep 4 '17 at 15:27
  • \$\begingroup\$ This sounds fishy. Don't you want to put the whole code for review? ;) \$\endgroup\$ – 301_Moved_Permanently Sep 4 '17 at 15:39
  • \$\begingroup\$ it might contain company secrets ;-) \$\endgroup\$ – amadain Sep 4 '17 at 17:42
  • \$\begingroup\$ >>> a=trythis(1,2,3,4) \$\endgroup\$ – amadain Sep 6 '17 at 7:34
  • 1
    \$\begingroup\$ @amadain Sorry, there was a missing import \$\endgroup\$ – 301_Moved_Permanently Sep 6 '17 at 12:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.