Make kwargs directly accessible - python

I am refactoring a piece of code, and I have run into the following problem. I have a huge parameter list, which now I want to pass as kwargs. The code is like this:
def f(a, b, c, ...):
print a
...
f(a, b, c, ...)
I am refactoring it to:
data = dict(a='aaa', b='bbb', c='ccc', ...)
f(**data)
Which means I have to do:
def f(**kwargs):
print kwargs['a']
...
But this is a pita. I would like to keep:
def f(**kwargs):
# Do some magic here to make the kwargs directly accessible
print a
...
Is there any straightforward way of making the arguments in the kwargs dict directly accessible, maybe by using some helper class / library?

There are some ways - but you can also wrap your function like this:
def f(**kwargs):
arg_order = ['a', 'b', 'c', ...]
args = [kwargs.get(arg, None) for arg in arg_order]
def _f(a, b, c, ...):
# The main code of your function
return _f(*args)
Sample:
def f(**kwargs):
arg_order = ['a', 'b', 'c']
args = [kwargs.get(arg, None) for arg in arg_order]
def _f(a, b, c):
print a, b, c
return _f(*args)
data = dict(a='aaa', b='bbb', c='ccc')
f(**data)
Output:
>>>
aaa bbb ccc

Well, you can update locals manually, but the documentation specifically warns against it.
for key, value in kwargs.iteritems(): #Python 2.7 here
locals()[key] = value
The other option is using exec, which though usually frowned on, is at least guaranteed to work correctly.
for key, value in kwargs.iteritems(): #Python 2.7 here
exec('{} = value'.format(key)
Though I wouldn't ever admit to anyone that you actually did either of these.

Inside the function:
for k, v in kwargs.iteritems():
locals()[k] = v

Another possible method (Based on your comment here) is to use an Attribute Dictionary:
class AttributeDict(dict):
def __getattr__(self, attr):
return self[attr] if attr in self.keys() else None
def __setattr__(self, attr, value):
self[attr] = value
To be used like this:
def f(**kwargs):
kwargs = AttributeDict(kwargs)
Sample:
def f(**kwargs):
kwargs = AttributeDict(kwargs)
print kwargs.a, kwargs.b, kwargs.c
data = dict(a='aaa', b='bbb', c='ccc')
f(**data)
Output:
>>>
aaa bbb ccc
Note: You can always name it x if you want a shorter variable name, then access would just be x.a

I have had to refactor my code in similar cases and I share your dislike of using kwargs['a'], which just feels a bit awkward. Sometimes I use a bunch object instead of a dictionary, which allows you to access fields by attribute access (params.a) instead of a dictionary lookup. It saves you only 3 characters of typing for every time you use a parameter, but I find it much better looking since you do not need quotes around the parameter name. There are various recipes around to implement one, see e.g. these ones.
So instead of using a dict like in your case, you would use it like
In [1]: class Bunch:
...: def __init__(self, **kwds):
...: self.__dict__.update(kwds)
In [2]: params = Bunch(a = 'aaa', b = 'bbb')
In [3]: def f(p):
...: print p.a
...:
In [4]: f(params)
aaa
I know this is not a direct answer to your question, but it is just an alternative for using kwargs.

It seems to me that you're doing redundand job, and easiest solution wouldb leave as it is. Those a, b, c are keyword arguments as well as positional, so you can call your function the way you like:
>>> def f(a, b, c=3):
... print a, b, c
With all keyword args
>>> f(**{'a':1, 'b':2})
1 2 3
With mix of positional and keyword args
>>> f(5, **{'b':4})
5 4 3
And get proper error in case of wrong keyword args
>>> f(**{'d':4, 'a':1, 'b':2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'd'

Related

Change only certain arguments in a class/method, hold others constant

I have a class & method, each with several arguments: my_class(a,b,c).my_method(d,e,f) and I'd like to be able to only change a single argument, while holding the others constant.
Constantly copy-pasting the other constant arguments seems bad, so I'd like to create a new object wrapper_fct where I reference my_class but only provide the one argument I want to change, b, without always having to specify the remaining arguments. How would wrapper_fct() look like?
For example, wrapper_fct(my_class, b1) would return my_class(a,b1,c).my_method(d,e,f), wrapper_fct(my_class, b2) would return my_class(a,b2,c).my_method(d,e,f).
Here's an example in practice:
Loop through just the variable b and evaluate several classes/methods for each new instance of b, and append the results in a list.
I can currently do this in a for loop:
mylist1 = [] # init lists (append results here)
mylist2 = []
mylist2 = []
for b in [1,2,3,4,5]:
mylist1.append( my_class1(a,b,c).my_method(d,e,f) )
mylist2.append( my_class2(a,b,c).my_method(d,e,f) )
mylist3.append( my_class3(a,b,c).my_method(d,e,f) )
...
But it seems better to create a function loop_through_B() and use the wrapper_fct(my_class,b) as specified above. Not sure if it's the ideal solution, but maybe something like:
def loop_through_B(input_class, b_values = [1,2,3,4,5])
mylist = []
for b in b_values:
mylist.append( wrapper_fct(input_class,b) )
return mylist
loop_through_B(my_class1) # would I also have to specify the method here as well?
loop_through_B(my_class2)
loop_through_B(my_class3)
Extra Question: how would I add the ability to vary method arguments, or even multiple class & method arguments?
After #chepner pointed me in the right direction, I think the best solution is to use the lambda function:
wrapper_fct = lambda b: my_class1(a,b,c).my_method(d,e,f)
In this case, I can vary b as much as I want while holding the class arguments a,c, and method arguments d,e,f constant. Note that with lambda functions, I can also vary the method arguments and/or the class arguments. For example:
wrapper_fct_multiple = lambda b, e: my_class1(a,b,c).my_method(d,e,f)
It is also possible to do this with functools.partial, but it's not obvious to me how I would specify both class & method arguments with functools.
Anyway, here is the solution implementation using lambda:
# define the "wrapper function" outside the loop
wrapper_fct = lambda b: my_class1(a,b,c).my_method(d,e,f)
# define the function I want to use to loop through B:
def loop_through_B(class_wrapper, b_values)
mylist = []
for b in b_values:
mylist.append( class_wrapper(b) )
return mylist
# run:
loop_through_B(wrapper_fct, b_values=[1,2,3,4,5])
# Can make additional wrapper_fct2, wrapper_fct3, for my_class2, my_class3 ...
You can pass the method a dictionary of arguments, and change what the method sees by selectively updating it when calling the method.
Here's what I mean:
class MyClass:
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def my_method(self, kwargs):
return sum((kwargs[key] for key in kwargs.keys()))
def __repr__(self):
classname = type(self).__name__
args = ', '.join((f'{v!r}' for v in (self.a, self.b, self.c)))
return f'{classname}({args})'
instance = MyClass('a','b','c')
print(instance) # -> MyClass('a', 'b', 'c')
kwargs = dict(d=1, e=2, f=3)
print(instance.my_method(kwargs)) # -> 6
print(instance.my_method(dict(kwargs, e=38))) # -> 42

python call a function with kwargs

I have a function:
def myfunc():
kwargs = {}
a = 1
b = 2
kwargs.update(a=a, b=b)
newfunc(**kwargs)
and my newfunc
def newfunc(**kwargs):
print a
Its not giving the value of a which is 1
whats wrong ?
It's because you didn't put any key, value in your dictionary, you should have written that :
def newfunc(**kwargs):
print kwargs["a"]
def myfunc():
kwargs = {"a" :1, "b": 2}
newfunc(**kwargs)
You can refer to this thread to understand kwargs better : Understanding kwargs in Python
You should add yor variables to your dictionary and print the item at position of the variable. To me it looks like your code should be written as:
def myfunc():
kwargs = {'a': 1,
'b': 2}
newfunc(**kwargs)
def newfunc(**kwargs):
print(kwargs['a'])
if(__name__ == '__main__'):
myfunc()
or your newfunc should have the arguments you want to fill with your kwargs dictionary like:
def myfunc():
kwargs = {'a': 1,
'b': 2}
newfunc(**kwargs)
def newfunc(a, b):
print(a)
if(__name__ == '__main__'):
myfunc()
Hope that helps.
You forgot to include the error. The Error would have been a NameError, a being undefined.
There's multiple things wrong with your code:
def myfunc():
kwargs = {}
a = 1
b = 2
this doesn't change the dictionary kwargs. This just created two new local names a and b. I guess what you wanted to have is:
kwargs = {}
kwargs["a"] = 1
kwargs["b"] = 2
EDIT: Your update does solve the issue above this line
Then:
def newfunc(**kwargs):
print a
Will give you an Error, because where should a come from?
Using **kwargs here just tells python to store all (not previously absorbed) arguments get stored in a new dictionary. What you hence want is either something like:
def newfunc(a,b):
print a
or
def newfunc(**kwargs):
print kwargs["a"]
Taking a look at your code, you seem to struggle with the concepts of how to deal with dictionaries. Maybe the question you're asking would be easier for you to answer yourself if your sat back and read a tutorial on python's dict
Two things.
First, your kwargs argument in myfunc is an empty dict, so you won't pass any parameters. If you want to pass the value of a and b to newfunc, you can either use
kwargs = {'a':1, 'b':2}
newfunc(**kwargs)
or
newfunc(a=1, b=2)
In both cases, you will get 'a' and 'b' in the kwargs dict of the newfunc function.
Second, you should extract your argument from the kwargs dict in newfunc. Something like print kwargs.get('a') should suffice.

Get the name of a python variable

def f(s)
print <name of s> = s
I wish to output "hello=10" for f(hello), given that the variable hello has value 10.
The problem is how to get the variable name of the variable, i.e., <name of s>?
This is for debugging purpose.
Say given a new statement s=2
f(s) before the new statement will print s=0 if s is initially 0, f(s) after the new statement will print s=2.
I can easily write:
def f(name,value)
print "%s=%s"%(name,str(value))
and use it as f("s",s), but that would need me to input two arguments, which is more cumbersome.
I am not sure if it is actually worth it, but using the information from frames for simple function calls with positional arguments you can do something like this:
import inspect
def hello(s1, s2, s3, s4):
args = inspect.getargspec(hello).args
line = inspect.getouterframes(inspect.currentframe())[1][4][0]
actual_args = map(str.strip, line[line.index('(')+1: line.index(')')].split(','))
for x, y in zip(args, actual_args):
print "value of {} is {}".format(y, locals()[x])
a, b, c, d = 1, 2, 3, 4
hello(a, b, c, d)
output:
value of a is 1
value of b is 2
value of c is 3
value of d is 4
You could (probably) do this using the traceback module.
import traceback
def some_func(x):
stack = traceback.extract_stack()
calling = stack[-2]
func_call_str = calling[-1]
print func_call_str
There are probably lots of caveats that I'm unaware of, but at least calling some_func(hello) should print some_func(hello) and you can just proceed and extract the "variable name" using string extraction methods.
If you have only one argument there for the function; then you can do this way.
def f(s):
var, val = locals.items()[0] # `var` will always have value 's', so no much difference here.
print "{} = {}".format(var, val)
In your case the passing argument ( Actual parameter ) name won't be available once it passed in to the function ( Formal parameter ).
You can do it this way:
def func(**kwargs):
for key in kwargs:
print 'Var <%s> with value <%s>' % (key, kwargs[key])
>>> func(say=2, qqq=3)
Var <say> with value <2>
Var <qqq> with value <3>

str.format() with lazy dict?

I want to use str.format() and pass it a custom lazy dictionary.
str.format() should only access the key in the lazy dict it needs.
Is this possible?
Which interface needs to be implemented by the lazy_dict?
Update
This is not what I want:
'{0[a]}'.format(d)
I need something like this:
'{a}'.format(**d)
Need to run on Python2.7
For doing '{a}'.format(**d), especially the **d part, the "lazy" dict is transformed into a regular one. Here happens the access to all keys, and format() can't do anything about it.
You could craft some proxy objects which are put in place of the elements, and on string access they do the "real" work.
Something like
class LazyProxy(object):
def __init__(self, prx):
self.prx = prx
def __format__(self, fmtspec):
return format(self.prx(), fmtspec)
def __repr__(self):
return repr(self.prx())
def __str__(self):
return str(self.prx())
You can put these elements into a dict, such as
interd = { k, LazyProxy(lambda: lazydict[k]) for i in lazydict.iterkeys()}
I didn't test this, but I think this fulfills your needs.
After the last edit, it now works with !r and !s as well.
You can use the __format__ method (Python 3 only). See the doc here.
If I understand your question correctly, you want to pass a custom dictionary, that would compute values only when needed. First, we're looking for implementation of __getitem__():
>>> class LazyDict(object):
... def __init__(self, d):
... self.d = d
... def __getitem__(self, k):
... print k # <-- tracks the needed keys
... return self.d[k]
...
>>> d = D({'a': 19, 'b': 20})
>>> '{0[a]}'.format(d)
a
'19'
This shows that only key 'a' is accessed; 'b' is not, so you already have your lazy access.
But also, any object attribute is usable for str.format this way, and using #property decorator, you can access function results:
class MyObject(object):
def __init__(self):
self.a = 19
self.b = 20
def __getitem__(self, var):
return getattr(self, var)
# this command lets you able to call any attribute of your instance,
# or even the result of a function if it is decorated by #property:
#property
def c(self):
return 21
Example of usage:
>>> m = MyObject()
>>> '{0[c]}'.format(m)
'21'
But note that this also works, making the formating string a little bit specific, but avoid the need for __getitem__() implementation.
>>> '{0.c}'.format(m)
'21'

Alias for dictionary operation in Python

I want to do something like this:
f[frozenset((1,3,4))] = 5
f[frozenset((1,))] = 3
but it's just painful to type these all the time, is there anyway to have alias for this? I know in C++ it's possible to have a helper function which return a reference so you can just type:
F(1,3,4) = 5
F(1) = 3
with F as a helper function. Thanks very much!
I think that this can really only be achieved via a subclass:
class FrozenSetDict(dict):
def __setitem__(self,idx,value):
try:
dict.__setitem__(self,frozenset(idx),value)
except TypeError:
dict.__setitem__(self,frozenset((idx,)),value)
d = FrozenSetDict()
d[1,2,3] = 4
d[1] = 5
print d
yields:
{frozenset([1, 2, 3]): 4, frozenset([1]): 5}
This introduces an asymmetry between __getitem__ and __setitem__ which could easily be fixed by re-defining __getitem__ in the same way.
This might seem a little messy -- Indeed it is. Why require a subclass? That just makes it harder to put non-frozenset objects into your dictionary as keys. You could easily use this recipe though to create a proxy object which will do this with your dict:
#I don't like the name of this class -- I'm open to suggestions :)
class FrozenSetProxy(object):
def __init__(self,obj):
self.obj = obj
def __setitem__(self,idx,value):
try:
self.obj[frozenset(idx)] = value
except TypeError:
self.obj[frozenset((idx,))] = value
def __getitem__(self,idx):
try:
return self.obj[frozenset(idx)]
except TypeError:
return self.obj[frozenset((idx,))]
d = dict()
F = FrozenSetProxy(d)
F[1,2,3] = 4
F[1] = 5
print d
print F[1]
There's nothing like a C++ reference in Python, and the syntax you use is illegal to boot (in the words of the parser: can't assign to function call). You could emulate it with an object or subclass dict to customize its __getitem__. But there's a simpler and less intrusive way: Pass the value to the helper too, and let it handle the assignment:
def blah(f):
def F(*args, value):
f[frozenset(args)] = value
F(1, 3, 4, value=5)
F(1, value=3)
Note that this uses a Python 3 feature, keyword-only parameters. If you need it to work with Python 2, you can emulate the call syntax by accepting **kwdargs:
def F(*args, **kwds):
# optional: check that no other keyword arguments were passed
f[frozenset(args)] = kwds['value']

Categories

Resources