How Can I Pythonically pass complex arguments to functions? - python

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.

Related

Is there any simple way to pass arguments based on their position, rather than kwargs. Like a positional version of kwargs?

Is there a generic python way to pass arguments to arbitrary functions based on specified positions? While it would be straightforward to make a wrapper that allows positional argument passing, it would be incredibly tedious for me considering how frequently I find myself needing to pass arguments based on their position.
Some examples when such would be useful:
when using functools.partial, to partially set specific positional arguments
passing arguments with respect to a bijective argument sorting key, where 2 functions take the same type of arguments, but where their defined argument names are different
An alternative for me would be if I could have every function in my code automatically wrapped with a wrapper that enables positional argument passing. I know several ways this could be done, such as running my script through another script which modifies it, but before resorting to that I'd like to consider simpler pythonic solutions.
For key arguments use **kwargs but for positional arguments use *args.

Python - Sending multiple values for one argument to a function

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)

Parameter vs Arguments ? finally,what are they?

I am a beginner in python programming and recently i came across functions,parameters,arguments and...
I have done a lot of research on Parameters and Arguments(Even checked the answers of similar past questions on StackOverflow)but i couldn't get their meanings.
Some say,parameters are variables which we give them to functions while we are defining them and arguments are values that are passed in function once we given them to the function in order to run the function.While some other say no,it's not like that.Parameters and Arguments are same and do the same task...
Can anyone tell me the meaning Parameters and Arguments in a clear way?
Are Parameters and Arguments considered variables?
For what kind of purpose do we use them?
Please don't explain too much complicated,i am a beginner.
Thank you so much.
Per the official documentation:
Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what types of arguments a function can accept. For example, given the function definition:
def func(foo, bar=None, **kwargs):
pass
foo, bar and kwargs are parameters of func. However, when calling func, for example:
func(42, bar=314, extra=somevar)
the values 42, 314, and somevar are arguments.
The glossary defines them as:
Argument: A value passed to a function (or method) when calling the function.
Parameter: A named entity in a function (or method) definition that specifies an argument (or in some cases, arguments) that the function can accept.
Python doesn't really have "variables" like some other languages - it has "names" referring to "objects". See e.g. "Code like a Pythonista" and "Facts and myths about Python names and values".
Take it this way:
Parameter:
A parameter represents a value that the procedure expects you to pass when you call it. The procedure's declaration defines its parameters.
Argument:
An argument represents the value that you pass to a procedure parameter when you call the procedure. The calling code supplies the arguments when it calls the procedure.
Example:
int add (int value1, int value2) // Here value1 and value2 are PARAMETERS.
{
return value1+value2;
}
Now while calling the function
answer = add(2,3); // Here values 2 and 3 are ARGUMENTS.
Same goes with Python, while declaration, they are parameters, while calling they are arguments.
Some may differ with what i have written, but this is how it is actually known in programming world.

Python default arguments and argument names

I was wondering if the 'a=a', and 'b=b' can lead to problems/unexpected behaviour? code works fine in the example.
def add_func(a=2,b=3):
return a+b
a=4
b=5
answer = add_func(a=a, b=b)
Thanks
Not that I know of, although I'd love to be proved wrong.
The formal language reference defines the lexical structure of a function call. The important bit is that it defines a "keyword_item" as identifier "=" expression. Also, here's what it says about how the arguments to the call are interpreted:
If keyword arguments are present, they are first converted to
positional arguments, as follows. First, a list of unfilled slots is
created for the formal parameters. If there are N positional
arguments, they are placed in the first N slots. Next, for each
keyword argument, the identifier is used to determine the
corresponding slot (if the identifier is the same as the first formal
parameter name, the first slot is used, and so on). If the slot is
already filled, a TypeError exception is raised. Otherwise, the value
of the argument is placed in the slot, filling it (even if the
expression is None, it fills the slot). When all arguments have been
processed, the slots that are still unfilled are filled with the
corresponding default value from the function definition.
This lists a few possible scenarios.
In the simple case, like you mentioned, where there are two formal arguments (a and b), and if you specify the function call using keyword parameters like add_func(a=a, b=b), here's what happens:
Two slots are created to hold the parameters.
Since you didn't provide any positional arguments in the call (just keyword arguments), none of the slots are filled initially.
Each of your keyword arguments are analyzed individually, and the identifier of your argument (the "a" in the a= part) is compared with all of the formal parameters names of the function (the names that were given the parameters when the function was defined, in our case, a and b).
When a match occurs, the value of the keyword arguments (in this case, 4!) is used to fill the corresponding slot.
This repeats until all keyword arguments are analyzed. If all slots aren't filled, then Python tries to assign a default value to the remaining slots if one exists. If not, an error is raised.
So, Python treats the "identifier" in a keyword argument completely differently. This is only true for keyword arguments, though; obviously, if you tried something like add_func(b, a), even though your parameters themselves are called b and a, this would not be mapped to the formal parameters in the function; your parameters would be backwards. However, add_func(b=b, a=a) works fine; the positions don't matter as long as they are keyword arguments.
It depends on whether or not the global objects pointed to are mutable or immutable. immutable objects such as your integers are copies when modified, so it's safe. Mutable objects such as lists are modified in-place, and are NOT safe to use this way. Any change to them persists between calls and may (and probably will) cause unexpected behaviors.
This:
a=[]
def f(a=a):
pass
Is the same as:
def f(a=[]):
pass
Which is a known bad practice in Python programs.

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