Confused about ** parameters in python function calls [duplicate] - python

This question already has answers here:
What does ** (double star/asterisk) and * (star/asterisk) do for parameters?
(25 answers)
Closed 9 years ago.
For reference I am referring to the answer in this post
The author of the answer gives the following code
def sum(*values, **options):
s = 0
for i in values:
s = s + i
if "neg" in options:
if neg:
s = -s
return s
s = sum(1, 2, 3, 4, 5) # returns 15
s = sum(1, 2, 3, 4, 5, neg=True) # returns -15
s = sum(1, 2, 3, 4, 5, neg=False) # returns 15
However when I run on mine I get the following error
NameError: global name 'neg' is not defined
Can anyone explain this. And in general, how does the function know when values ends and when options begins

if neg:
That line is buggy. It should be:
if options["neg"]:
How does the function know when values ends and when options begins?
Unnamed values go in *values. Keyword arguments go in **options.

You have made a small mistake. Change your code to the following and it should work. Just get the value of "neg" from the options dictionary, (values holds the unnamed arguments and options holds the keyword arguments)
>>> def sum(*values, **options):
s = 0
for i in values:
s = s + i
if "neg" in options:
if options["neg"]:
s = -s
return s
>>> s = sum(1, 2, 3, 4, 5, neg=True)
>>> s
-15
>>> sum(1, 2, 3, 4, 5)
15
>>> sum(1, 2, 3, 4, 5, neg=True)
-15
>>> sum(1, 2, 3, 4, 5, neg=False)
15
Although, as #glglgl pointed out, changing your code to the following consumes both the if statements into one.
>>> def sum(*values, **options):
s = 0
for i in values:
s = s + i
if options.get("neg", False):
s = -s
return s
How does get(...) work?
If the options dictionary doesn't have a key "neg", (as handled by your first if condition), then, get(...) returns the default value of False and s is not negated, and if options contains "neg", then it's value is returned, in which case, s is negated depending on the value in the dictionary.

Related

How to store calculated values?

I have been trying to write code which gives the solution of the number of ways of reaching a sum, which is specified. This is very similar to the subset sums problem which I have found online (Finding all possible combinations of numbers to reach a given sum).
I modified slightly the code so that it reuses numbers multiple times.
object_list = [(2, 50), (3, 100), (5, 140)] # the first number in the tuple is my weight and the second is my cost
max_weight = 17
weight_values = [int(i[0]) for i in object_list]
cost_values = [int(i[1]) for i in object_list]
def subset_sum(objects, max_weight, weights=[]):
w = sum(weights)
if w == max_weight:
print("sum(%s)=%s" % (weights, max_weight))
if w >= max_weight:
return
for i in range(len(objects)):
o = objects[i]
subset_sum(objects, max_weight, weights + [o])
if __name__ == "__main__":
subset_sum(weight_values, max_weight)
print(subset_sum(weight_values, max_weight))
This gives the solution:
sum([2, 2, 2, 2, 2, 2, 2, 3])=17
sum([2, 2, 2, 2, 2, 2, 3, 2])=17
sum([2, 2, 2, 2, 2, 2, 5])=17
sum([2, 2, 2, 2, 2, 3, 2, 2])=17
...
So on so forth.
Unlike the original I am using a list of tuples and then taking the first value of the tuple to make a list. This is the same with the last value.
The part I am currently stuck on is how to store these values and reuse them in the next part of the code.I had a look at this post but I couldn't understand it (Python: How to store the result of an executed function and re-use later?).
So I want to store the part of the solution which stores [2, 2, 2, 2, 2, 2, 2, 3] from the solution sum([2, 2, 2, 2, 2, 2, 2, 3])=17. I want to do this for all solutions because in the next step i am going to replace the numbers with the next part of the tuple (so 2 will be replaced by 50 because the tuple that 2 is in is (2,50)). Then I am going to use this to print another sum value with the replaced numbers and print the highest value (probably going to sort the solutions from highest to lowest and print the first value in the list)
I tried using a dictionary to try and replace the values after the calculation but i couldn't manage to do it.
I tried:
dictionary = dict(zip(weight_values, cost_values))
Any help is appreciated. before anyone asks Ia have looked online for solutions and have no one else to ask help from, since the only person i know who has a background in coding is my brother who isn't at home

Why does unpacking give results in tuple

I am learning arbitrary value parameter and reading stackoverflow this and this answers and other tutorials I already understood what *args and **kwargs do in python but I am facing some errors. I have two doubts, first one is:
If I run this code print(w) then I am getting this output:
def hi(*w):
print(w)
kl = 1, 2, 3, 4
hi(kl)
output:
((1, 2, 3, 4),)
but if I run this code with print(*w) then I am getting this output:
code:
def hi(*w):
print(*w)
kl = 1, 2, 3, 4
hi(kl)
output:
(1, 2, 3, 4)
My second doubt is:
je = {"a": 2, "b": 4, "c": 6, 4: 5}
for j in je:
print(*je)
output
b a 4 c
b a 4 c
b a 4 c
b a 4 c
What exactly is *je doing there? How is it working in iteration?
When you use * in declaration of the arguments def hi(*w):, it means that all the arguments will be compressed to the tuple, e.g.:
hi(kl, kl) # ((1, 2, 3, 4), (1, 2, 3, 4))
After when you use print(*w) * run unpack of your tuple.
je={"a":2,"b":4,"c":6,4:5}
for j in je:
print(*je)
In every iteration you use unpack of your dict (you use je and get the keys of your dict like [j for j in je])
https://docs.python.org/2/tutorial/controlflow.html#tut-unpacking-arguments
Your first case, it's because you're passing kl into the function as a tuple, not as arbitrary values. Hence, *w will expand into a single element tuple with kl as the first value.
You're essentially calling:
hi((1, 2, 3, 4))
However, what I suspect you want is
hi(1, 2, 3, 4)
# or in your case
hi(*kl)
When printing in python 3, print is a function, so again. When w is a tuple and you call it like:
print(w)
# you'll get the tuple printed:
# (1, 2, 3, 4)
However, again, you can call it with arguments like:
print(1, 2, 3, 4)
# or in your case
print(*w)
# 1 2 3 4
For your second part, look at it converted to a list first:
list({"a":2,"b":4,"c":6,4:5})
# ["b", "a", 4, "c"]
# Note, dictionaries are unordered and so the list could be in any order.
If you were to then pass that to print using the * expansion:
print("b", "a", 4, c)
# or in your case
print(*["b", "a", 4, "c"])
Just note, that the * does the default iteration for you. If you wanted some other values, use je.values() or je.items()

Can omit optional arguments and use *args in a Python function? [duplicate]

This question already has answers here:
Python, default keyword arguments after variable length positional arguments
(2 answers)
Closed 9 years ago.
I am trying to figure out if I can leave an optional argument out (use it's default value) when using *args in Python. The following code works through "print(a)", so explicitly including the optional argument h in the deriv function call works. Can I leave it (h) out somehow? My attempts ("b = ...", "c = ...", "d = ...") fail. Is there another way?
def deriv(f, x, h=1.e-9, *params):
return (f(x+h, *params)-f(x-h, *params))/(2.*h)
def f1(x, a, p):
return a*x**p
a = deriv(f1, 3, 1.e-9, 4, 5)
print(a)
b = deriv(f1, 3, , 4, 5)
c = deriv(f1, 3, 4, 5)
d = deriv(f1, 3, h, 4, 5)
No, python applies positional arguments to all named arguments first; h is the third positional argument in the function signature and thus only argument positions 4 and over are captured by *params.
Instead, use a **kwargs argument catching arbitrary keyword arguments and look h up in that:
def deriv(f, x, *params, **kwargs):
h = kwargs.pop('h', 1.e-9)
You'll now have to name h explicitly when calling deriv:
b = deriv(f1, 3, 4, 5, h=2.0)
It looks like you're using Python 3, so you can use keyword-only arguments:
>>> def deriv(f, x, *params, h=1.0E-9):
print(f)
print(x)
print(params)
print(h)
>>> deriv(pow, 'x', 10, 20, 30)
<built-in function pow>
x
(10, 20, 30)
1e-09
>>> deriv(pow, 'x', 10, 20, 30, h=.2)
<built-in function pow>
x
(10, 20, 30)
0.2

Is there such thing as "apply" in python?

I wonder, is there a function in python -let's call it now apply- that does the following:
apply(f_1, 1) = f_1(1)
apply(f_2, (1, 2)) = f_1(1, 2)
...
apply(f_n, (1, 2,..., n)) = f_n(1, 2,..., n) # works with a tuple of proper length
Since it does exist in eg. A+ and Mathematica and it used to be really useful for me.
Cheers!
Python has language-level features for this, known as "argument unpacking", or just "splat".
# With positional arguments
args = (1, 2, 3)
f_1(*args)
# With keyword arguments
kwargs = {'first': 1, 'second': 2}
f_2(**kwargs)
You can use the * operator for the same effect:
f_1(*(1, 2)) = f_1(1, 2)
...
The expression following the * needn't be a tuple, it can be any expression that evaluates to a sequence.
Python also has a built-in apply function that does what you'd expect, but it's been obsolete in favor of the * operator since Python 2.3. If you need apply for some reason and want to avoid the taint of deprecation, it is trivial to implement one:
def my_apply(f, args):
return f(*args)
Yep, use the * operator on the list of arguments. For a practical example:
max(1, 2, 3, 4, 5) # normal invocation
=> 5
max(*[1, 2, 3, 4, 5]) # apply-like invocation
=> 5
Think of the second snippet as equivalent to apply(max, [1, 2, 3, 4, 5])

How to convert list to tuple and search for max and min

I am currently learning python coding and I come across this qns on a learning site:
Create a function that takes a sequence of inputs (may be a list, a tuple, or just a bunch of inputs). The function should return the minimum and the maximum of the list.
This are some of the test values that are using:
minmax(5,4)
4,5
minmax(5,4,8,3,5,7,4)
3,8
minmax([5,4,6])
4,6
minmax(5.1,4.2,68.34,129.1,-90.3)
-90.3,129.1
And I had tried doing it this way but when the parameter is a list, I can't seems to convert it into a tuple and find the max and min.
Here is what I had tried:
def minmax(*a):
b = tuple(a)
minNum = min(b)
maxNum = max(b)
c = (minNum, maxNum)
return c
When a list is taken in, the return result is ([5, 4, 6], [5, 4, 6])
def minmax(*a):
if len(a) == 1: # Single (list, tuple, or scalar) argument
try:
return minmax(*a[0]) # Expansion of sequence elements, if possible
except TypeError: # Case of minmax(42)
pass # The general code below handles this directly
return (min(a), max(a))
>>> minmax(3, 5, 1, 10)
(1, 10)
>>> minmax([3, 5, 1, 10])
(1, 10)
>>> minmax((42, 123, -12))
(-12, 123)
>>> minmax(42)
42
This works in more cases than the built-in min() and max(), which do not work on a single scalar argument (min(42)).
>>> min(42)
TypeError: 'int' object is not iterable
It is however possible to write a simpler version that behaves like the built-in min() and max() (see my other answer, for instance).
This works by forcing min() to be given strictly more than 1 element, except in the special case of minmax(42), which calls min((42,)).
To be able to handle different ways of passing in arguments, you need to have some conditions to handle each case.
>>> def minmax(*a):
... if len(a) == 1 and hasattr(a[0], '__getitem__'):
... # handle a single sequence passed in
... return min(a[0]), max(a[0])
... # handle values passed in
... return min(a), max(a)
...
>>> minmax(5, 4)
(4, 5)
>>> minmax(5, 4, 8, 3, 5, 7, 4)
(3, 8)
>>> minmax([5, 4, 6])
(4, 6)
>>> minmax(5.1, 4.2, 68.34, 129.1, -90.3)
(-90.3, 129.1)
A simpler but slightly less powerful solution that mirrors my other solution is:
def minmax(*a):
if len(a) == 1: # Single (list or tuple) argument
return (min(a[0]), max(a[0]))
return minmax(a) # Single tuple argument given to minmax()
This version forces min() to be given a single (list or tuple) argument.
It behaves like the built-in min() and max(): min(42) and minmax(42) both raise an exception:
>>> minmax(3, 5, 1, 10)
(1, 10)
>>> minmax([3, 5, 1, 10])
(1, 10)
>>> minmax((42, 123, -12))
(-12, 123)
>>> minmax(42)
TypeError: 'int' object is not iterable
>>> min(42)
TypeError: 'int' object is not iterable

Categories