Python - Sending multiple values for one argument to a function - python

I am a few days new to Python so if this is silly please excuse me..
Is there a method to sending multiple variables to a single function? As an example:
pe.plot_chart(conn,7760,'DataSource1',123,save=True)
This above function takes a connection to SQL where it pulls data for unique ID 7760 from datasource1 (uniqueid 123). Can I use some method to send multiple criteria for the DataSource1 field? e.g.
pe.plot_chart(conn,7760,['DataSource1','DataSource2'],[123,345],save=True)
pe.plot_chart was created by me, so any modifications that have to be made to it to make it work are fine
Is this type of operation possible to perform?
EDIT: Adding on some extra info.
The plot_chart function.. well it plots a chart, and saves it to the location above. Each call of the function produces one graph, I was hoping that by sending multiple values for a parameter I could have the function dynamically add more series to the plot.
So if I send 4 data sources to the function, I will end up with 4 lines on the plot. For this reason I am not sure looping through a data source collection would be good (will just produce 4 plots with one line?)

Yes you can send multiple arguments to a function in python, but that shouldn't be a surprise. What you cannot do is having positional arguments after a keyword argument, that is calls like f(1, foo=2, 3) is not allowed (your example is invalid for that reason).
Also you cannot supply multiple values to a single argument in a strict sense, but you can supply an list or tuple to a single argument, that is for example f(1, foo=(2, 3)) is acceptable and your function might interpret that as you are supplying two values to the foo argument (but in reality it's only one tuple).
The downside is that the function must be able to distinguish between a tuple as argument and what is intended as a single argument. The easiest way is to insist on that the argument should be a tuple or at least iterable. The function would have to look somewhat like:
def f(foo, bar):
for x in foo:
do_something(bar, x)
f(bar=fubar, foo=(arg1, arg2, arg3))
f((arg1, arg2, arg3), bar=fubar) # same as previous line
f((arg1, arg2, arg3), fubar) # same as previous line
another more advanced alternative would be to use keyword argument for everything except what would be the multiple arguments by using variable argument list, but this is somewhat clumpsy in python2 as you'll need to supply all arguments as positional unless you manually unpack the keywords arguments, in python3 there is some relief as you can force using of keyword arguments:
def f(*args, bar=fubar):
for x in args:
do_something(bar, x)
f(arg1, arg2, arg3, bar=fubar)
# f(fubar, arg1, arg2, arg3) is not allowed
and then every argument that is not a keyword argument (still those positional arguments has to be the first arguments) will end up in args, and the bar argument is required to be passed as keyword argument.
In python2 the above would need to be:
def f(*args, **kwds):
bar = kwds.get("bar", fubar)
for x in args:
do_something(bar, x)

data_sources = [data_source1, data_source2, data_source3]
for source in data_sources:
pe.plotchart(connection, uniqueid = 7760, source...)
There's various ways to approach this - if you want to send an iterable (like a list) to your function once and have the function iterate through them, you can do that. You can also call the function from a loop. If the other parameters are going to change for each iteration, look into "zip", which is useful for pairing data for looping.

It's possible to pair data source specifications with unique IDs in your case. Here is a simple approach with lists of tuples:
def myFunc(values):
for v in values:
print v[0], v[1]
myFunc([("hello", 1), ("world", 2)])
The list elements could also be expanded into classes if there is a need for more description for each line to plot. The benefit of this flip is that you are handling one list of line descriptors (which are represented by tuples), not loosely coupled "arguments".
The output BTW is this:
hello 1
world 2
Your specific case would change into this
pe.plot_chart(conn,7760,[('DataSource1',123),('DataSource2',345)],save=True)

Related

How Can I Pythonically pass complex arguments to functions?

I have a SOAP web service I have to work with, and one of the commands it supports is a "SQL like" query where I input a select, from, and where statements. I think the "where" clause will be most demonstrative of what I'm trying to do so here:
def sql_soap(tablename, where):
sql_where = [soap_object(where_statement) for where_statement in where]
return query
sql_soap('student',where=[{'Condition':'=','Field':'Subject','Value':'Calculus'}])
Basically, the way I've thought to do this is to package a list of where-clause dictionaries. But the dictionaries should always have the same keys. Is there a way to define this type in the function definition? I don't want kwargs or args because I know in advance the data structure.
One thing I looked at was
def sql_soap(tablename, *, where):
Apparently this is only available in newer versions of Python (which I have) but my understanding is the where clause after this is expecting a dictionary, and I want a list of dictionaries.
Generally speaking how do I define a function argument, when I want a dictionary inside of a list, or something else nested? Is there any way besides a dictionary, that I can get a single function parameter (where) to accept all of the arguments I need to make the SOAP where object?
I do not know if this helps, but you could use *where to expect an arbitrary amount of args:
def sql_soap(tablename, *where):
sql_where = [soap_object(where_statement) for where_statement in where]
return query
sql_soap('student',
{'Condition':'=','Field':'Subject','Value':'Calculus'},
{'Condition':'=','Field':'Subject2','Value':'Calculus2'},
)
One thing you can also do, but you would to have to change probaply a lot of code for that, is use namedtuple instead of dictionaries:
from collections import namedtuple
wheretuple = namedtuple("wheretuple", "field condition value")
sql_soap('student', wheretuple("Subject", "=", "Calculus"))
You have not specified anything about types. The * syntax in a function definition only specifies how a caller can provide arguments for the parameters. Parameters before it can be filled with both positional arguments and keyword arguments, those that follow the * can only be specified with keyword arguments.
Put differently, the following calls are now legal:
sql_soap('student', where=[...]) # one positional, one keyword argument
sql_soap(tablename='student', where=[...]) # two keyword arguments
but the following is not:
sql_soap('student', [...]) # two positional arguments
You'll instead get a TypeError exception, TypeError: sql_soap() takes 1 positional argument but 2 were given.
Using * in a function definition does not say anything about what type of objects the parameter accepts. You can still pass anything you like to the function call.
Perhaps you got confused with the *args and **kwargs syntax in function definitions, where those parameters capture all remaining positional or keyword arguments passed in, which did not address any of the other parameters. They don't say anything about the argument types either; instead they put those remaining argument values in a tuple and dictionary, respectively.
Python does now support type hinting, but even type hinting will not let you specify what keys to use in a dictionary.
I'd use named tuples instead here, together with type hints:
from typing import NamedTuple, Sequence
class WhereClause(NamedTuple):
condition: str
field: str
value: str
def sql_soap(tablename: str, where: Sequence[WhereClause]):
...
This lets the type checker know that the where argument must be a sequence type (like a list), that contains only WhereClause instances. And those instances will have specific attributes.
Anytime you want to use any of the WhereClause instances, you can use attributes to get at the contents, so whereclause.condition and whereclause.value.

How do I use the output of a function as the input for another function inside a function

I am currently studying Python and hoping someone could help me.
I'm fairly new to coding so would be really helpful if its explained well.
Lets say I have a function called function1 that returns:
return (row, column)
Now I am writing another function called say function2.
Within this function I need to call say:
exampleItem.exampleName(row, column, name)
How do I use the output of the function1 which is the row and column as the row and column arguments in the above line from function2?
I really hope this makes sense as I got seriously penalized for not writing a question properly before because I didn't realize the best practice here.
In all versions of Python you can do:
row, column = function1()
exampleName(row, column, name)
In more recent versions (3.5+) you can use unpacking to do things like:
exampleName(*function1(), name)
For further reading see: PEP 448 -- Additional Unpacking Generalizations.
You can use the star * to unpack the result --which is a tuple-- of calling function function1 after you place it as an argument inside the other function. This results in providing the elements returned from function1 as positional arguments in the second:
exampleItem.exampleName(*function1(), name)
In Python 2.x you can do the same but, you need to supply the remaining positional arguments in keyword form in order to get it to work with *:
exampleItem.exampleName(*function1(), name=name)
this also works on Python 3.x so you have no issues with portability.
Of course, unpacking in a previous statement with row, column = function1() and then providing them to the second function by position with:
exampleItem.exampleName(row, column, name)
is another option. It really falls down to preference in the end :-)
This is called "tuple unpacking", which takes the parts of a tuple (or list) and uses them as arguments.
If f1(row, column) is the function returning (row, column) (for the sake of a simple example):
a = f1(3, 4)
b = exampleItem.exampleName(*a, name="fish")
# Or, as a one-liner:
b = exampleItem.exampleName(*f1(3,4), name="fish")
The major limitation here is that after the tuple-unpacked argument (a above, you unpack with a *), all further arguments must be assigned by name, not position.
If you can't give specific argument names, and if you're still using Python 2, you'd need to do something like:
x, y = f1(3, 4) # unpacks the length-2 tuple into two variables
b = exampleItem.exampleName(x, y, "fish")
Which cannot be made into a one-liner. Python 3 doesn't require that, though, so you could do the first example without needing to use name= on the third argument if you're in Python 3.
Another relevant note: If you use ** instead, you can unpack a dictionary, which does the same thing as tuple unpacking except for named arguments instead of positional arguments.

passing default parameters with varargs in function in python

when I try the following, I get error in the function definition itself.
>>> def mymap(*seq,pad=None):
File "<stdin>", line 1
def mymap(*seq,pad=None):
SyntaxError: invalid syntax
I am trying to give default value for the parameter pad.
but then, I tried this and it works (for wrong reason):
>>> def mymap(pad=None,*seq):
... print seq
>>> mymap([1,2,3],[4,5,6])
([4, 5, 6],)
[(4,)]
>>>
It is not printing the tuple of seq which should be ([1,2,3],[4,5,6]).
What you really want here is for pad to be a keyword-only parameter.
One of the major changes in 3.0 was designing function parameters and arguments to, among other things, provide exactly the feature you're looking for. In fact, your first version works exactly as you'd hope it to. But if you want to stick with 2.x, you don't get any of the new features in 3.x.
So, you have no choice but to fake it:
def mymap(*seq, **kwargs):
pad = kwargs.pop('pad', None)
if kwargs:
raise TypeError("mymap() got an unexpected keyword argument '{}'".format(kwargs.keys()[0]))
print seq
This does very nearly the exact same thing as the equivalent Python 3 code. Of course it's more complicated, a lot less clear, slower, and opaque to introspection… all of which is exactly why keyword-only parameters were added in 3.0. But if you need it, and you can't use Python 3.x, this is what you have to do. (Just be glad you're not trying to write C-API functions.)
You seem to be mixing up two entirely independent things: a parameter having a default value (which I assume is what you mean by the term "default parameter", which doesn't really mean anything), and being keyword-only.
You've already got a parameter with a default value, in your second version. You can see that easily: call mymap(), and it succeeds, with pad getting its default value of None (and seq being empty).
But you want it to also be a keyword-only parameter, so it doesn't steal the first positional argument. And that's the part you can't do in 2.x.
If you want to know more, PEP 3012 explains the keyword-only feature, and Arguments and parameters attempts to gather all the relevant documentation links, and summarize how everything fits together.
It looks like you are inputting the args [1,2,3] and [4,5,6] as two seperate arguments. They are not in a tuple.
mymap([1,2,3], [4,5,6]) << in this example, from your code, the [1,2,3] is being passed in for pad and the [4,5,6] is being passed in for seq. That's why printing seq results in [4,5,6]
Also, named arguments like pad, must come before *args or **kwargs.
For example:
mymap(None, [1,2,3],[4,5,6]) #passing None for pad
prints:
([1,2,3], [4,5,6])

Using named arguments with variable length un-named arguments in Python

I apologize if this question has already been asked/answered, I would have expected that to be the case but was unable to find any related questions...
I'd like to create a python function that takes two mandatory arguments, one named argument, and some unknown number of other, non-named arguments as so:
def my_function(arg1, arg2, arg3=None, *other_args):
pass
Is this possible in Python 2.x?
Can a function accept named arguments in addition to a variable length argument list?
I believe the answer is 'no', in which case I'm thinking the only solution available to me would be something similar to the following:
def my_function(arg1, arg2, **kwargs):
arg3 = kwargs["arg3"]
other_args = kwargs["other_args"]
Is that correct?
That syntax is certainly valid, but I think you mean can you write the function signature such that arg3 is only bound if it's used as a named parameter (e.g. my_function(1, 2, arg3 = 3)), and otherwise to have all arguments past the first two be caught by *other_args, in which case the answer is no. Optional arguments can be specified by naming, but they're also positional like normal arguments and are filled in if enough parameters are available, before resorting to catch-alls like *args or **kwargs.
I would probably write it exactly as you did, using keyword arguments

Advantages of using *args in python instead of passing a list as a parameter

I'm going through python and I was wondering what are the advantages of using the *args as a parameter over just passing a list as a parameter, besides aesthetics?
Generally it's used to either pass a list of arguments to a function that would normally take a fixed number of arguments, or in function definitions to allow a variable number of arguments to be passed in the style of normal arguments. For instance, the print() function uses varargs so that you can do things like print(a,b,c).
One example from a recent SO question: you can use it to pass a list of range() result lists to itertools.product() without having to know the length of the list-of-lists.
Sure, you could write every library function to look like this:
def libfunc1(arglist):
arg1 = arglist[1]
arg2 = arglist[2]
...
...but that defeats the point of having named positional argument variables, it's basically exactly what *args does for you, and it results in redundant braces/parens, since you'd have to call a function like this:
libfunc1([arg1val,arg2val,...])
...which looks very similar to...
libfunc1(arg1val,arg2val,...)
...except with unnecessary characters, as opposed to using *args.
That is for flexibility.
It allows you to pass on the arguments, without knowing how much you need. A typical example:
def f(some, args, here): # <- this function might accept a varying nb of args
...
def run_f(args, *f_args):
do_something(args)
# run f with whatever arguments were given:
f(*f_args)
Make sure to check out the ** keyword version.

Categories

Resources