3

I'm currently creating an object like this:

class Obj(object):
    def __init__(self,**kwargs):
        params = ['val1','val2','val3','val4',...]
        for p in params:
            setattr(self,p,kwargs.get(p,None))

I'm doing this so I don't have to do this:

class Obj(object):
    def __init__(self,val1=None,val2=None,val3=None,val4=None,...):
        self.val1=val1
        self.val2=val2
        self.val3=val3
        self.val4=val4
        ...

My question is, can you do a mix of the two? Where I can define the expected parameters yet still loop the parameters to set the attributes? I like the idea of defining the expected parameters because it is self documenting and other developers don't have to search for what kwargs are used.

I know it seems pretty petty but I'm creating an object from some XML so I'll be passing in many parameters, it just clutters the code and bugs me.

I did google this but couldn't find anything, probably because dictionary and kwargs together point to kwarg examples.

UPDATE: To be more specific, is it possible to get a dictionary of passed in parameters so I don't have to use kwargs at all?

Sudo code:

class Obj(object):
    def __init__(self,val1=None,val2=None,val3=None,val4=None,...):
        for k,v in dictionary_of_paramters.iteritems():
            setattr(self,k,v)
rjbez
  • 802
  • 2
  • 12
  • 21

6 Answers6

2

You can use the inspect module:

import inspect

def myargs(val1, val2, val3=None, val4=5):
        print inspect.currentframe().f_locals

it shows all the locals available on the current stack frame.

myargs('a','b')
==>  {'val3': None, 'val2': 'b', 'val1': 'a', 'val4': 5}

(note: it's not guaranteed to be implemented on all Python interpreters)

edit: i concur that it's not a pretty solution. what i would do is more like:

def _yourargs(*names):
        "returns a dict with your named local vars"
        alllocs = inspect.stack()[1][0].f_locals
        return {n:alllocs[n] for n in names}

def askformine(val1, val2, val3=None, val4=5):
        "example to show just those args i'm interested in"
        print _yourargs('val1','val2','val3','val4')

class Obj(object):
        "example inserting some named args as instance attributes"
        def __init__(self, arg1, arg2=4):
                 self.__dict__.update(_yourargs('arg1','arg2'))

edit2 slightly better:

def pickdict(d,*names):
    "picks some values from a dict"
    return {n:d[n] for n in names}

class Obj(object):
    "example inserting some named args as instance attributes"
    def __init__(self, arg1, arg2=4):
        self.__dict__.update(pickdict(locals(),'arg1','arg2'))
Javier
  • 60,510
  • 8
  • 78
  • 126
  • Thanks! This is what I'm looking for. From my understanding it should work with all pure python interpreters, just not things like jython, etc...correct? – rjbez Dec 27 '12 at 16:37
  • Ugh, are you sure this is worth it? Keep in mind, f_locals will have more than just the arguments, and this will make your code odd and magical. Just spell out the arguments you need. – Ned Batchelder Dec 27 '12 at 16:42
  • odd and magical... you can encapsulate in a function to hide the ugliness. more than the args: you can call your utility function with a list of var names you're interested in. worth it? i don't think so. – Javier Dec 27 '12 at 16:45
  • @rjbez: it's the same thing... i had totally forgot about `locals()`. shows how little i like about this :-) – Javier Dec 27 '12 at 17:01
  • @NedBatchelder Ok thanks, you guys convinced me it isn't worth it. – rjbez Dec 27 '12 at 17:06
1

There is no nice way to get a dictionary of all the arguments to a function. The **kwargs syntax only collects up the extra keyword arguments, not the ones that match explicit parameters in the function definition.

Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
1

Although you won't be able to get the parameters without using kwargs or the inspect module (see other answers), you can do something like this...

class Obj(object):
    def __init__(self, **kwargs):
        self.__dict__.update(**kwargs)

Every object has a dictionary that stores all of the attributes, which you can access via self.__dict__. Then you're just using update to set all of the attributes in that object's internal dictionary.

See this question on some discussion of this method.

Community
  • 1
  • 1
Mark Hildreth
  • 42,023
  • 11
  • 120
  • 109
1

If you want to obtain the args dict at the very top of your method, before you define any locals, this is as simple as:

class Obj(object):
    def __init__(self,val1=None,val2=None,val3=None,val4=None):
       kwargs = dict(locals())

To read this dict later on, some introspection magic is required:

class Obj(object):
    def __init__(self,val1=None,val2=None,val3=None,val4=None):

       # feel free to add more locals

       loc = dict(locals())
       fun = sys._getframe().f_code
       kwargs = {x:loc[x] for x in fun.co_varnames[:fun.co_argcount]}

You can also make the latter reusable by adding a function like this:

def getargs():
    f = sys._getframe(1)
    return {x:f.f_locals[x] for x in f.f_code.co_varnames[:f.f_code.co_argcount]}

and then:

class Obj(object):
    def __init__(self,val1=None,val2=None,val3=None,val4=None):

       # feel free to add more locals

       kwargs = getargs()

This is cpython-specific, I guess.

georg
  • 211,518
  • 52
  • 313
  • 390
0

Yes you can mix the two. See below:

def method(a, b=1, *args, **kwargs):
    '''some code'''

This is valid. Here:

'a' is a required argument
'b' is a default argument
'args' will have all the non-keyword arguments and
'kwargs' will have all the keyword arguments.

Example:

method(1,2,3,4,5,test=6,test1=7)

This call will have:

a=1
b=2
args=(3,4,5)
kwargs={'test':6,'test1':7}
GautamJeyaraman
  • 545
  • 2
  • 11
0

A kind of an ugly workaround: Inject extra arguments into kwargs and use it where you want to loop over all keyword arguments (PS this is an example usage of the inspect module, but not recommended for production use):

#!/usr/bin/env python

import inspect

def inject_defaults(func):
    """ injects '__defaults' key into into kwargs, 
        so it can be merged with kwargs in the decorated method """

    args, varargs, varkwargs, defaults = inspect.getargspec(func)
    have_defaults = args[-len(defaults):]
    defaults_dict = dict(zip(have_defaults, defaults))

    def fun(*args, **kwargs):
        kwargs['__defaults'] = defaults_dict
        return func(*args, **kwargs)

    return fun

@inject_defaults
def f1(a,b,c, x=1, **kwargs):
    kwargs.update(kwargs['__defaults'])
    del kwargs['__defaults']

    for k, v in kwargs.items():
        # here, x, y and z will appear
        print(k, v)

f1(1, 2, 3, y=3, z=2)
# prints
# ('y', 3)
# ('x', 1)
# ('z', 2)
miku
  • 181,842
  • 47
  • 306
  • 310