Conditionally including multiple keyword arguments in a function call - python

What is the 'pythonic' way to implement a waterfall if-statement like situation like this, when it applies to kwargs? I'm trying to avoid a situation where c=None is added to kwargs (because having c in the kwargs keys causes all sorts of problems downstream).
def run_something(**kwargs):
print(kwargs)
def func(a = None, b=None, c=None):
if a and b and c:
run_something(a=a,b=b,c=c)
elif a and b:
run_something(a=a,b=b)
elif a:
run_something(a=a)
else:
run_something()
I know the easy answer is that I could just do:
def func(**kwargs):
run_something(**kwargs)
however my particular use case doesn't make this easy

First of all, instead of doing tests like if a: I am guessing what you really want to do is only pass the a argument of it isn't None so testing if a is not None: will pass a value for a that is 0 or an empty string, i.e. anything other than the default. So this is what I would do:
def run_something(**kwargs):
print(kwargs)
def func(a = None, b=None, c=None):
d = {}
if a is not None:
d['a'] = a
if b is not None:
d['b'] = b
if c is not None:
d['c'] = c
run_something(**d)
func(c=7, b=4)
Prints:
{'b': 4, 'c': 7}
Note that the above handles any combination of passed arguments, which your "waterfall" if statements do not. If you really want to test for any non-False value as in your original code, then of course you can do:
def run_something(**kwargs):
print(kwargs)
def func(a=None, b=None, c=None):
d = {}
if a:
d['a'] = a
if b:
d['b'] = b
if c:
d['c'] = c
run_something(**d)
func(c=7, b=4)

You can do something like this:
def run_something(**kwargs):
print(kwargs)
def func(a=None, b=None, c=None):
kwargs = {"a": a, "b": b, "c": c}
run_something(**{key_word: arg for key_word, arg in kwargs.items() if arg is not None})
func() # {}
func("a") # {'a': 'a'}
func("a", "b") # {'a': 'a', 'b': 'b'}
func("a", "b", "c") # {'a': 'a', 'b': 'b', 'c': 'c'}
func(b="b") # {'b': 'b'}
func("a", c="c") # {'a': 'a', 'c': 'c'}
func(b="b", c="c") # {'b': 'b', 'c': 'c'}
func(c="c") # {'c': 'c'}
this will handle any combination of a b and c
I used dictionary comprehension.
in case you don't know what it means you can check this out python dictionary comprehension

Short and sweet:
def foo(a=None, b=None, c=None):
run_something(
**({ 'a': a } if a else {}),
**({ 'b': b } if b else {}),
**({ 'c': c } if c else {}),
)
Whether it’s ‘pythonic’, I will not say. Personally, I find it a meaningless notion, vague and ad-hoc at best and self-contradictory at worst.

Related

Creating a dict from a set of variables using their names [duplicate]

I quite regularly want to create a dictionary where keys are variable names. For example if I have variables a and b I want to generate: {"a":a, "b":b} (typically to return data at the end of a function).
Are there any (ideally built in) ways in python to do this automatically? i.e to have a function such that create_dictionary(a,b) returns {"a":a, "b":b}
Have you considered creating a class? A class can be viewed as a wrapper for a dictionary.
# Generate some variables in the workspace
a = 9; b = ["hello", "world"]; c = (True, False)
# Define a new class and instantiate
class NewClass(object): pass
mydict = NewClass()
# Set attributes of the new class
mydict.a = a
mydict.b = b
mydict.c = c
# Print the dict form of the class
mydict.__dict__
{'a': 9, 'b': ['hello', 'world'], 'c': (True, False)}
Or you could use the setattr function if you wanted to pass a list of variable names:
mydict = NewClass()
vars = ['a', 'b', 'c']
for v in vars:
setattr(mydict, v, eval(v))
mydict.__dict__
{'a': 9, 'b': ['hello', 'world'], 'c': (True, False)}
You can write your own function for create_dict
def create_dict(*args):
return dict({i:eval(i) for i in args})
a = "yo"
b = 7
print (create_dict("a", "b"))
Which gives {'a': 'yo', 'b': 7} output.
Here's a simple generator for the same:
vars = ["a", "b"]
create_dict = {i:eval(i) for i in args}
or you can use this one-liner lambda function
create_dict = lambda *args: {i:eval(i) for i in args}
print (create_dict("a", "b"))
But if you want to pass the variables to the function instead of the variable name as string, then its pretty messy to actually get the name of the variable as a string. But if thats the case then you should probably try using locals(), vars(), globals() as used by Nf4r
Extending on the code of #Nf4r, I use something like:
a, b = 1, 2
def make_dict(*args):
# Globals will change of size, so we need a copy
g = {k: v for k, v in globals().items() if not k.startswith('__')}
result = {}
for arg in args:
for k, v in g.items():
try:
if v == arg:
result[k] = v
except ValueError:
continue # objects that don't allow comparison
return result
make_dict(a, b)
Have you tried something like:
a, b, c, d = 1, 2, 3, 4
dt = {k:v for k, v in locals().items() if not k.startswith('__')}
print(dt)
{'a': 1, 'd': 4, 'b': 2, 'c': 3}

packing named arguments into a dict

I know I can turn function arguments into a dictionary if the function takes in **kwargs.
def bar(**kwargs):
return kwargs
print bar(a=1, b=2)
{'a': 1, 'b': 2}
However, is the opposite true? Can I pack named arguments into a dictionary and return them? The hand-coded version looks like this:
def foo(a, b):
return {'a': a, 'b': b}
But it seems like there must be a better way. Note that i am trying to avoid using **kwargs in the function (named arguments work better for an IDE with code completion).
It sounds like you are looking for locals:
>>> def foo(a, b):
... return locals()
...
>>> foo(1, 2)
{'b': 2, 'a': 1}
>>> def foo(a, b, c, d, e):
... return locals()
...
>>> foo(1, 2, 3, 4, 5)
{'c': 3, 'b': 2, 'a': 1, 'e': 5, 'd': 4}
>>>
Note however that this will return a dictionary of all names that are within the scope of foo:
>>> def foo(a, b):
... x = 3
... return locals()
...
>>> foo(1, 2)
{'b': 2, 'a': 1, 'x': 3}
>>>
This shouldn't be a problem if your functions are like that given in your question. If it is however, you can use inspect.getfullargspec and a dictionary comprehension to filter locals():
>>> def foo(a, b):
... import inspect # 'inspect' is a local name
... x = 3 # 'x' is another local name
... args = inspect.getfullargspec(foo).args
... return {k:v for k,v in locals().items() if k in args}
...
>>> foo(1, 2) # Only the argument names are returned
{'b': 2, 'a': 1}
>>>

Combine two dictionaries of dictionaries (Python)

Is there an easy way to combine two dictionaries of dictionaries in Python? Here's what I need:
dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}}
result = dict_union(dict1, dict2)
# => result = {'A' : {'B' : 'C', 'D' : 'E'}}
I created a brute-force function that does it, but I was looking for a more compact solution:
def dict_union(train, wagon):
for key, val in wagon.iteritems():
if not isinstance(val, dict):
train[key] = val
else:
subdict = train.setdefault(key, {})
dict_union(subdict, val)
Here is a class, RUDict (for Recursive-Update dict) that implements the behaviour you're looking for:
class RUDict(dict):
def __init__(self, *args, **kw):
super(RUDict,self).__init__(*args, **kw)
def update(self, E=None, **F):
if E is not None:
if 'keys' in dir(E) and callable(getattr(E, 'keys')):
for k in E:
if k in self: # existing ...must recurse into both sides
self.r_update(k, E)
else: # doesn't currently exist, just update
self[k] = E[k]
else:
for (k, v) in E:
self.r_update(k, {k:v})
for k in F:
self.r_update(k, {k:F[k]})
def r_update(self, key, other_dict):
if isinstance(self[key], dict) and isinstance(other_dict[key], dict):
od = RUDict(self[key])
nd = other_dict[key]
od.update(nd)
self[key] = od
else:
self[key] = other_dict[key]
def test():
dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}}
dx = RUDict(dict1)
dx.update(dict2)
print(dx)
if __name__ == '__main__':
test()
>>> import RUDict
>>> RUDict.test()
{'A': {'B': 'C', 'D': 'E'}}
>>>
This solution is pretty compact. It's ugly, but you're asking for some rather complicated behavior:
dict_union = lambda d1,d2: dict((x,(dict_union(d1.get(x,{}),d2[x]) if
isinstance(d2.get(x),dict) else d2.get(x,d1.get(x)))) for x in
set(d1.keys()+d2.keys()))
My solution is designed to combine any number of dictionaries as you had and could probably be cut down to look neater by limiting it to combining only two dictionaries but the logic behind it should be fairly easy to use in your program.
def dictCompressor(*args):
output = {x:{} for mydict in args for x,_ in mydict.items()}
for mydict in args:
for x,y in mydict.items():
output[x].update(y)
return output
You could subclass dict and wrap the original dict.update() method with a version which would call update() on the subdicts rather than directly overwriting subdicts. That may end up taking at least as much effort as your existing solution, though.
Has to be recursive, since dictionaries may nest. Here's my first take on it, you probably want to define your behavior when dictionaries nest at different depths.
def join(A, B):
if not isinstance(A, dict) or not isinstance(B, dict):
return A or B
return dict([(a, join(A.get(a), B.get(a))) for a in set(A.keys()) | set(B.keys())])
def main():
A = {'A': {'B': 'C'}, 'D': {'X': 'Y'}}
B = {'A': {'D': 'E'}}
print join(A, B)
As for me there is not enaugh information but anyway please find my sample code below:
dict1 = {'A' : {'B' : 'C'}}
dict2 = {'A' : {'D' : 'E'}, 'B':{'C':'D'}}
output = {}
for key in (set(dict1) | set(dict2):
output[key] = {}
(key in dict1 and output[key].update(dict1.get(key)))
(key in dict2 and output[key].update(dict2.get(key)))

how could I pass a collection of the arguments that a function needed?

For example, I have a function:
def foo(a, b, c):
pass
Now I have a dict:
d = {'a': 1, 'b': 2, 'c': 3}
I have to write something like:
foo(d['a'], d['b'], d['c'])
I'd like to know, could I just pass a collection of the arguments(like d) to the function?
Sure, you can pass a dict as kwargs:
def foo(a, b, c):
print a, b, c
d = {'a': 1, 'b': 2, 'c': 3}
foo(**d)
Output:
1 2 3
Use the dictionary unpacking operator (**):
foo(**d)
foo(**d) should work.
See http://docs.python.org/tutorial/controlflow.html#unpacking-argument-lists

How to iterate over function arguments

I have a Python function accepting several string arguments def foo(a, b, c): and concatenating them in a string.
I want to iterate over all function arguments to check they are not None. How it can be done?
Is there a quick way to convert None to ""?
Thanks.
locals() may be your friend here if you call it first thing in your function.
Example 1:
>>> def fun(a, b, c):
... d = locals()
... e = d
... print e
... print locals()
...
>>> fun(1, 2, 3)
{'a': 1, 'c': 3, 'b': 2}
{'a': 1, 'c': 3, 'b': 2, 'e': {...}, 'd': {...}}
Example 2:
>>> def nones(a, b, c, d):
... arguments = locals()
... print 'The following arguments are not None: ', ', '.join(k for k, v in arguments.items() if v is not None)
...
>>> nones("Something", None, 'N', False)
The following arguments are not None: a, c, d
Answer:
>>> def foo(a, b, c):
... return ''.join(v for v in locals().values() if v is not None)
...
>>> foo('Cleese', 'Palin', None)
'CleesePalin'
Update:
'Example 1' highlights that we may have some extra work to do if the order of your arguments is important as the dict returned by locals() (or vars()) is unordered. The function above also doesn't deal with numbers very gracefully. So here are a couple of refinements:
>>> def foo(a, b, c):
... arguments = locals()
... return ''.join(str(arguments[k]) for k in sorted(arguments.keys()) if arguments[k] is not None)
...
>>> foo(None, 'Antioch', 3)
'Antioch3'
def func(*args):
' '.join(i if i is not None else '' for i in args)
if you're joining on an empty string, you could just do ''.join(i for i in args if i is not None)
You can use the inspect module and define a function like that:
import inspect
def f(a,b,c):
argspec=inspect.getargvalues(inspect.currentframe())
return argspec
f(1,2,3)
ArgInfo(args=['a', 'b', 'c'], varargs=None, keywords=None, locals={'a': 1, 'c': 3, 'b': 2})
in argspec there are all the info you need to perform any operation with argument passed.
To concatenate the string is sufficient to use the arg info received:
def f(a,b,c):
argspec=inspect.getargvalues(inspect.currentframe())
return ''.join(argspec.locals[arg] for arg in argspec.args)
For reference:
http://docs.python.org/library/inspect.html#inspect.getargvalues
Is this perhaps what you'd like?
def foo(a, b, c):
"SilentGhost suggested the join"
' '.join(i if i is not None else '' for i in vars().values())
def bar(a,b,c):
"A very usefull method!"
print vars()
print vars().values()
Notice the use of vars(), which returns a dict.
I would use sed s/None//g, but that's not in python, but you can probably use os.popen() to do that.

Categories

Resources