Unpacking argument lists:
def send(*data):
for datum in data:
ser.write(datum)
vs sending in a list in the first place:
def send(data):
for datum in data:
ser.write(datum)
When it simplifies the API for the case where otherwise you'll always have to pass in a list:
send(something, otherthing)
versus:
send([something, otherthing])
where your usual parameters are taken from different locations; e.g. something and otherthing are more likely to be separate variables than already collected in one list.
The Python 3.x print() function does exactly that, as well as the os.path.join() function. You rarely have all your print arguments or path-elements-to-join combined in a list before calling the API.
Compare:
os.path.join(rootdirectory, relativepath, filename)
print('Debug information:', localvariable)
vs.
os.path.join([rootdirectory, relativepath, filename])
print(['Debug information:', localvariable])
If .join() or print() were to accept only one positional argument (a list), the users of the API would find themselves typing the [ and ] brackets over and over again.
By accepting a variable number of positional arguments, you save users of your API the trouble of having to create list just for the function call. In the rare cases where the parameters have already been collected into a list, they can use the *params calling convention:
send(*params)
to echo your function signature.
Related
I am new to python and came across a problem.
I have to get a dictionary from the user via a function that has few elements (key-value). At the same time, if the user is not providing the dictionary, they can provide individual elements as arguments.
How do I check if user has provided dictA, if not, src msg. If the user doesn't provides either one of them, return an error back to the calling function.
Lets say:
def myfunc(dictA, src, msg, e=True)
where dictA is the dictonary and src, msg are arguements if the user didn't provide dictA.
I wouldn't support them passing in dictA or the other arguments. I would just have them pass in dictA as kwargs if that want to use that. Then you can just check if the values have been there
New function prototype might be
def myfunc(src, msg, e=True)
And then usages could be
myfunc(a_source, a_msg)
Or
myfunc(**dictA)
This way your function will automatically check that those values are at least present. And any additional value checking can be done on only one input
For example the following call would fail (without you needing to do anything else) because it would still be missing the positional argument of msg.
myfunc(**{'src': 'a_source'})
Similarly it will fail if you send too many positional arguments as well (i.e. passing in a positional argument and a kwarg via a dictionary)
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)
I have already found various answers to this question (eg. lambda function acessing outside variable) and all point to the same hack, namely (eg.) lambda n=i : n*2 with i a variable in the external scope of lambda (hoping I'm not misusing the term scope). However, this is not working and given that all answers I found are generally from couple of years ago, I thought that maybe this has been deprecated and only worked with older versions of python. Does anybody have an idea or suggestion on how to solve this?
SORRY, forgot the MWE
from inspect import getargspec
params = ['a','b']
def test(*args):
return args[0]*args[1]
func = lambda p=params : test(p)
I expected the signature of func to be ['a','b'] but if I try
func(3,2)
I get a Type error (TypeError: <lambda>() takes at most 1 argument (2 given) )
and it's true signature (from getargspec(func)[0] ) is ['p']
In my real code the thing is more complicated. Shortly:
def fit(self, **kwargs):
settings = self.synch()
freepars = self.loglike.get_args()
func = lambda p=freeparams : self.loglike(p)
minuit = Minuit(func,**settings)
I need lambda because it's the only way I could think to create inplace a function object depending on a non-hardcoded list of variables (extracted via a method get_params() of the instance self.loglike). So func has to have the correct signature, to match the info inside the dict settings
The inspector gives ['p'] as argument of func, not the list of parameters which should go in loglike. Hope you can easily spot my mistake. Thank you
There's no way to do exactly what you want. The syntax you're trying to use to set the signature of the function you're creating doesn't do what you want. It instead sets a default value for the argument you've defined. Python's function syntax allows you to define a function that accepts an arbitrary number of arguments, but it doesn't let you define a function with argument names in a variable.
What you can do is accept *args (or **kwargs) and then do some processing on the value to match it up with a list of argument names. Here's an example where I turn positional arguments in a specific order into keyword arguments to be passed on to another function:
arg_names = ['a', 'b']
def foo(*args):
if len(args) != len(arg_names):
raise ValueError("wrong number of arguments passed to foo")
args_by_name = dict(zip(arg_names, args))
some_other_function(**args_by_name)
This example isn't terribly useful, but you could do more sophisticated processing on the args_by_name dict (e.g. combining it with another dict), which might be relevant to your actual use case.
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.
Is there a way to pass a list as a function argument to eval() Or do I have to convert it to a string and then parse it as a list in the function?
My simple example looks like:
eval("func1(\'" + fArgs + "\')")
I'm just not sure if there is a better way of taking fArgs as a list instead of a string
Note:
The list is provided from a JSON response
EDIT: Ok here's a bit more of my class so there's a better understanding of how I'm using eval
def test(arg):
print arg
#Add all allowed functions to this list to be mapped to a dictionary
safe_list = ['test']
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])
class Validate:
def __init__(self, Value, fName, fArgs):
eval(fName + "(\'" + fArgs + "\')", {"__builtins__":None},safe_dict)
I may be wrong in thinking this, but to my understanding this is a safe use of eval because the only functions that can be called are the ones that are listed in the safe_list dictionary. The function to be run and the arguments for that function are being extracted out of a JSON object. The arguments are to be structured as a list, Will joining the list together with ", " be interpreted as actual arguments or just a single argument?
If you're using Python 2.6.x, then you should be able to use the json module (see py doc 19.2). If not, then there is python-json available through the python package index. Both of these packages will provide a reader for parsing JSON data into an appropriate Python data type.
For your second problem of calling a function determined by a message, you can do the following:
def foo():
print 'I am foo!'
def bar():
pass
def baz():
pass
funcs = {'func_a':foo, 'func_b':bar, 'func_c':baz}
funcs['func_a']()
This approach can be a bit more secure than eval because it prevents 'unsafe' python library functions from being injected into the JSON. However, you still need to be cautious that the data supplied to your functions can't be manipulated to cause problems.
Specifying parameters the following way works:
root#parrot$ more test.py
def func1(*args):
for i in args:
print i
l = [1,'a',9.1]
func1(*l)
root#parrot$ python test.py
1
a
9.1
so, no direct need for eval(), unless I'm misunderstanding something.
Using a library to parse JSON input may be a better approach than eval, something like:
import json
func1(json.loads(fArgs))
Assert-ing that user input is correct would be a good idea, too.
The others have a good point, that you shouldn't be using eval. But, if you must:
eval("func1(%s)" % ", ".join(fArgs))
will call the function with all the arguments in the list. This:
eval("func1([%s])" % ", ".join(fArgs))
will call it with the list of arguments in just one argument. Maybe you even want this?
eval("func1([%s])" % ", ".join(map(eval, fArgs)))
which would eval the arguments as well?