Pass function outputs as arguments to another function [duplicate] - python

This question already has answers here:
Expanding tuples into arguments
(5 answers)
Closed 5 months ago.
I need to pass the output of a function in Python as an argument to another function. The only catch is that the 1st function returns a tuple and the entire tuple needs to be passed to the second function.
For ex:
I have a library function:
def some_function(some_string, some_number):
# does something
return (text,id)
Now I want to pass text and id returned from the some_function as arguments to another function. The catch is that the function has other arguments as well. I also need need to retrieve many texts and ids that will be generated by different values of some_string and number.
Depending on the condition met, I want to call another function which will look something like this
if abcd:
other_function(name,phone,**some_function("abcd","123")**,age)
elif xyz:
other_function(name,phone,**some_function("xyz","911")**,age)
else:
other_function(name,phone,**some_function("aaaa","000")**,age)
So what should I replace some_function("abcd","123") with so that it expands the tuple and sends both the text and id as arguments?
The other_function is defined like this
def other_function(name, phone, text, id, age):
...
return
Will simply passing some_function("aaaa","000") as an argument work?
I am asking this because I wrote a simple code to test my hypothesis and it was giving me a blank output.
def f(id,string):
return ("123", "hello")
def a(name, age, id, words, phone):
print name + age + id + words + phone
def main():
a("piyush", "23", f("12", "abc"), "123")

You have two options*; either explicitly unpack the function result first:
id, words = f('12', 'abc')
a('piyush', '23', id, words, '123')
or use tuple unpacking within the call to a, and supply the last parameter by keyword:
a('piyush', '23', *f('12', 'abc'), phone='123')
If this syntax is unfamiliar, see What does ** (double star) and * (star) do for parameters?
Note that if you try to pass phone as a positional argument (rather than a keyword argument as above) after the unpacked results from f, i.e.
a('piyush', '23', *f('12', 'abc'), '123')
you will get SyntaxError: only named arguments may follow *expression. You can't have positional arguments after * unpacking, you must use the keywords for any additional arguments.
* Unless you either:
rearrange the order of the parameters, as Oz123 suggests; or
wait for Python 3.5's release, as noted by abarnert.

You could also change the order of arguments in the signature:
def a(name, age, id, words, phone):
name = name
age=age
id=id
words=words
phone=phone
print name+age+id+words+phone
a("piyush", "23", 123, *f("12","123"))
piyush23123123hello
This way you can unpack the returned values directly when calling the function.
Note, however, that this makes readability very poor and debugging harder.
Also if you don't want to use unpacking and change the you call your function you can change the code like this:
def a(name, age, id_words, phone):
name = name
age=age
id=id_words[0]
words=id_words[1]
phone=phone
print name+age+id+words+phone
a("piyush", "23", f("12","123"), "123")
piyush23123hello123
This has the advatage of keeping all the functions call the same as they were. Only the interal works of the function change, not the signature.

Assuming you live in the future, the right way to do this is to just unpack the tuple into the middle of the argument list, like this:
a('piyush', '23', *f('12', 'abc'), '123')
Unfortunately, you, the OP, probably don't live in the future, so this is largely only helpful for people who find this answer after September 2015 (or are willing to require a pre-release version of Python before that). This functionality was added as PEP 448, which doesn't go in until Python 3.5 (and, as of 13 April 2015, doesn't even have a patch in trunk yet—but if you really want to live dangerously, you can download the latest patch at #2292, apply it to a local fork of the repo, and build it yourself…).
So, for the time being, you have to fake it, e.g., as in jonrsharpe's answer.

Related

Access default keyword argument value within a decorator [duplicate]

For this function
def eat_dog(name, should_digest=True):
print "ate dog named %s. Digested, too? %" % (name, str(should_digest))
I want to, external to the function, read its arguments and any default values attached. So for this specific example, I want to know that name has no default value (i.e. that it is a required argument) and that True is the default value for should_digest.
I'm aware of inspect.getargspec(), which does give me information about arguments and default values, but I see no connection between the two:
ArgSpec(args=['name', 'should_digest'], varargs=None, keywords=None, defaults=(True,))
From this output how can I tell that True (in the defaults tuple) is the default value for should_digest?
Additionally, I'm aware of the "ask for forgiveness" model of approaching a problem, but unfortunately output from that error won't tell me the name of the missing argument:
>>> eat_dog()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: eat_dog() takes at least 1 argument (0 given)
To give context (why I want to do this), I'm exposing functions in a module over a JSON API. If the caller omits certain function arguments, I want to return a specific error that names the specific function argument that was omitted. If a client omits an argument, but there's a default provided in the function signature, I want to use that default.
Python3.x
In a python3.x world, you should probably use a Signature object:
import inspect
def get_default_args(func):
signature = inspect.signature(func)
return {
k: v.default
for k, v in signature.parameters.items()
if v.default is not inspect.Parameter.empty
}
Python2.x (old answer)
The args/defaults can be combined as:
import inspect
a = inspect.getargspec(eat_dog)
zip(a.args[-len(a.defaults):],a.defaults)
Here a.args[-len(a.defaults):] are the arguments with defaults values and obviously a.defaults are the corresponding default values.
You could even pass the output of zip to the dict constructor and create a mapping suitable for keyword unpacking.
looking at the docs, this solution will only work on python2.6 or newer since I assume that inspect.getargspec returns a named tuple. Earlier versions returned a regular tuple, but it would be very easy to modify accordingly. Here's a version which works with older (and newer) versions:
import inspect
def get_default_args(func):
"""
returns a dictionary of arg_name:default_values for the input function
"""
args, varargs, keywords, defaults = inspect.getargspec(func)
return dict(zip(args[-len(defaults):], defaults))
Come to think of it:
return dict(zip(reversed(args), reversed(defaults)))
would also work and may be more intuitive to some people.
Depending on exactly what you need, you might not need the inspect module since you can check the __defaults__ attribute of the function:
>>> eat_dog.__defaults__
(True,)
>>> eat_dog.__code__.co_argcount
2
>>> eat_dog.__code__.co_varnames
('name', 'should_digest')
>>>
>>> eat_dog.__kwdefaults__
>>> eat_dog.__code__.co_kwonlyargcount
0
You can use inspect module with its getargspec function:
inspect.getargspec(func)
Get the names and default values of a Python function’s arguments. A tuple of four things is returned: (args, varargs, keywords, defaults). args is a list of the argument names (it may contain nested lists). varargs and keywords are the names of the * and ** arguments or None. defaults is a tuple of default argument values or None if there are no default arguments; if this tuple has n elements, they correspond to the last n elements listed in args.
See mgilson's answer for exact code on how to retrieve argument names and their default values.
To those looking for a version to grab a specific default parameter with mgilson's answer.
value = signature(my_func).parameters['param_name'].default
Here's a full working version, done in Python 3.8.2
from inspect import signature
def my_func(a, b, c, param_name='apple'):
pass
value = signature(my_func).parameters['param_name'].default
print(value == 'apple') # True
to take care of keyword-only args (and because defaults and kwonlydefaults can be None):
spec = inspect.getfullargspec(func)
defaults = dict(zip(spec.args[::-1], (spec.defaults or ())[::-1]))
defaults.update(spec.kwonlydefaults or {})
You can get this via some of the __dunder__ vars as mentioned by other posts. Putting that into a simple helper function can get you a dictionary of default values.
.__code__.co_varnames: A tuple of all input variables
.__defaults__: A tuple of the default values
It is worth noting that this tuple only incudes the default provided variables which must always be positioned last in the function arguments
You can use these two items to match the last n variables in the .__code__.co_varnames with all the items in the .__defaults__
EDIT Thanks to #griloHBG - Added if statement to prevent exceptions when no defaults are specified.
def my_fn(a, b=2, c='a'):
pass
def get_defaults(fn):
if fn.__defaults__==None:
return {}
return dict(zip(
fn.__code__.co_varnames[-len(fn.__defaults__):],
fn.__defaults__
))
print(get_defaults(my_fn))
Should give:
{'b': 2, 'c': 'a'}
In python, all the arguments with default value come after the arguments without default value. So the mapping should start from the end till you exhaust the default value list. Hence the logic:
dict(zip(reversed(args), reversed(defaults)))
gives the correctly mapped defaults.

Passing a list as arguments to function python

Scenario: I am trying to pass a list of strings to a function, as variable inputs.
Issue: Since the variables in the list are just for one of the arguments, I get the error must be str, not list.
The function hakes three inputs as arguments:
transformfile(path, name, id)
I have a list of names I want to pass:
list_names =['Name1', 'Name2', 'Name3']
I tried passing it directly, but got the aforementioned error...
transformfile(path, list_names, id)
Objective: In this case, my objective would be to make the function run multiple times, for each of the names in list_names.
Question: Is it possible to do this kind of procedure, or do I have to simply call the function directly multiple times?
The function isn't made to receive multiple names, it can only handle single names and there is no way to call it that will change that.
Luckily, this is exactly what for loops are for:
for name in list_names:
transformfile(path, name, id)
This is perfectly fine, normal, etc. Even if the function could receive a list, it'd probably have a for loop internally to do that.
You could also use list comprehensions if transformFile returns something:
result = [transformfile(path, n, id) for n in list_names]
Or even map builtin function with or without functools.partial:
result = map(lambda n: transformFile(path, n, id), list_names)
from functools import partial
result = map(partial(path=path, id=id), list_names)
Note: map returns an iterable so, if you want the result of each call, you need to create a list: list(result)

Dynamically update all instances of multiple input function

I'm creating a program with a class that has 3 input attributes. The program calls a function that creates many of these objects with their inputs being given based on some other criteria not important to this question.
As I further develop my program, I may want to add more and more attributes to the class. This means that I have to go and find all instances of the function I am using to create these objects, and change the input arguments.
For example, my program may have many of these:
create_character(blue, pizza, running)
where inputs correspond to character's favorite color, food, and activity. Later, I may want to add a fourth input, such as favorite movie, or possibly a fifth or sixth or ninety-ninth input.
Do professional programmers have any advice for structuring their code so that they don't have to go through and individually change each line that the create_character function is called so that it now has the new, correct number of inputs?
Find and replace seems fine, but this makes error possible, and also seems tedious. I'm anticipating calling this function at least 50 times.
I can think of a few options for how you could design your class to make easier to extend later new kinds of "favorite" things.
The first approach is to make most (or all) of the arguments optional. That is, you should specify a default value for each one (which might be None if there's not a real value that could apply as a default). This way, when you add an extra argument, the existing places that call the function without the new argument will still work, they'll just get the default value.
Another option would be to use a container (like a dictionary) to hold the values, rather than using a separate variable or argument for each one. For instance, in your example could represent the character's favorites using a dictionary like favorites = {'color': blue, 'food': pizza, 'activity': running} (assuming the those values are defined somewhere), and then you could pass the dictionary around instead of the separate items. If you use the get method of the dictionary, you can also make this type of design use default values (favorites.get('movie') will return None if you haven't updated the code that creates the dictionary to add a 'movie' key yet).
You can take advantage of argument/keyword argument unpacking to support dynamically-changing function parameters. And also factory function/classes that generate the function you need:
def create_character(required1, required2, *opt_args, **kwargs):
""" create_character must always be called with required1 and required2
but can receive *opt_args sequence that stores arbitrary number of
positional args. kwargs hold a dict of optional keyword args """
for i, pos_arg in enumerate(opt_args):
# pos_arg walks opt_args sequence
print "position: {}, value: {}".format(i+3, pos_arg)
for keyword, value in kwargs:
print "Keyword was: {}, Value was: {}".format(keyword, value)
pos_args = (1,2,3)
create_character('this is required','this is also required', *pos_args)
""" position: 3, value: 1
position: 4, value: 2
position: 5, value: 3 """
a_dict = {
'custom_arg1': 'custom_value1',
'custom_arg2': 'custom_value2',
'custom_arg3': 'custom_value3'
}
create_character('this is required','this is also required', **a_dict)
""" Keyword was: custom_arg2, value: custom_value2
Keyword was: custom_arg3, value: custom_value3
Keyword was: custom_arg1, value: custom_value1 """
I really like the list or dictionary input method, but it was still messy and allowed for the possibility of error. What I ended up doing was this:
I changed the class object to have no inputs. Favorites were first assigned with random, default, or unspecified options.
After the class object was created, I then edited the attributes of the object, as so:
self.favorite_movie = "unspecified"
self.favorite_activity = "unspecified"
new_character = (character())
new_character.favorite_movie = "Dr. Strangelove"
I think that the downside to this approach is that it should be slower than inputting the variables directly. The upside is that this is easy to change in the future. Perhaps when the program is finished, it will make more sense to then convert to #Blckknight 's method, and give the input as a list or dictionary.

Python helptip change text

I'm working on some basic Python code but I've got a problem I've never been able to solve.
Indeed, I would like to change the helptip of a function and I can totally do it.
First of all, here is my code (for example) :
def main(arg1,arg2,arg3):
#some blabla
return 1
The thing is, if I start calling this function (in IDLE for example), I have an helptip appearing which just get the same syntax as my function is defined : main(arg1,arg2,arg3).
I would prefer to have something like main(Responsible name, Responsible nickname, Responsible telephone), which represents way more better what each args are. I've already try some docstring implementation but I can only get the two lines together but not remove the first one with the arg1 ...
Can someone tell me if there is a way to get what I want ?
Two things:
If you want to have named arguments, that's fine; define your function with them: def main(name, nickname, phone):; and
For docstrings, the format isn't # (comment), it's """ (multiline string).
For example:
def myfunc(name, age, height):
"""Returns a growth-rate for the person based on age and height."""
return height / age
Now the "tooltip" reads:
(name, age, height)
Returns a growth-rate for the person based on age and height.
If you don't want any named arguments to be shown, you can use *args:
def myfunc(*args):
"""Takes three arguments: name, age and height, returns a growth rate."""
name, age, height = args
return height / age
Now the tooltip is:
(...)
Takes three arguments: name, age and height, returns a growth rate.
well, basically you can't do what you say you want. You need to change the prototype of your function to match what you want to see in the doc, even though that means using very long variable names in your code.
If you take the Zen of python (explicit is better than implicit), you need to choose carefully your variable names, so they are explicit and not too long. So in either cases, your name choice is bad, arg0,arg1,arg2 is awful because it means nothing, and Responsible name... is too long (and I won't even mention that it makes no sense including a space).
And then, it's part of the docstring to give a sentence for each argument to say what that argument is all about, typically:
def afunc(foo, bar):
"""This is a function
#param foo this is the first parameter
#param bar this is the second paramater
"""
pass

Dynamic Keyword Arguments in Python?

Does python have the ability to create dynamic keywords?
For example:
qset.filter(min_price__usd__range=(min_price, max_price))
I want to be able to change the usd part based on a selected currency.
Yes, It does. Use **kwargs in a function definition.
Example:
def f(**kwargs):
print kwargs.keys()
f(a=2, b="b") # -> ['a', 'b']
f(**{'d'+'e': 1}) # -> ['de']
But why do you need that?
If I understand what you're asking correctly,
qset.filter(**{
'min_price_' + selected_currency + '_range' :
(min_price, max_price)})
does what you need.
You can easily do this by declaring your function like this:
def filter(**kwargs):
your function will now be passed a dictionary called kwargs that contains the keywords and values passed to your function. Note that, syntactically, the word kwargs is meaningless; the ** is what causes the dynamic keyword behavior.
You can also do the reverse. If you are calling a function, and you have a dictionary that corresponds to the arguments, you can do
someFunction(**theDictionary)
There is also the lesser used *foo variant, which causes you to receive an array of arguments. This is similar to normal C vararg arrays.
Yes, sort of.
In your filter method you can declare a wildcard variable that collects all the unknown keyword arguments. Your method might look like this:
def filter(self, **kwargs):
for key,value in kwargs:
if key.startswith('min_price__') and key.endswith('__range'):
currency = key.replace('min_price__', '').replace('__range','')
rate = self.current_conversion_rates[currency]
self.setCurrencyRange(value[0]*rate, value[1]*rate)

Categories

Resources