I am trying to learn python reduce function.
This is some code that does not make sense to me
>>> x = [1,2,3]
>>> reduce(sum, [b for b in x if b > 0])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> reduce(sum, [b for b in x if b > 2])
3
Why does it work when b > 2 but not b > 0
The code seems almost exactly the same
In the second case the list you created with list comprehension has only one element, so it is not reduced. This reduction doesn't fail, because there is nothing to be reduced there, so function sum isn't executed and it doesn't fail. The result is the only integer, that has been there before.
The function sum is defined in the documentation. As you can see, it is supposed to sum values in some iterable container, such as list or tuple. You could write sum([1,2,3]) and it would work just like this reduction, that you want to make. If you want to achieve this result with using the reduction function, you have to make sure, that you have a function, that takes two integers and returns their sum.
Here you have such function:
def sum2(x, y):
return x + y
Reduce with such function should give you expected results.
sum2 = lambda x, y: x+y
The following function does the same, but is shorter and nicer due to lambda notation, it might be nice to look at it sometime in the future, if you don't know it yet.
From the docs about reduce:
Apply function of two arguments cumulatively to the items of iterable,
from left to right, so as to reduce the iterable to a single value
So in the first case, you have used reduce wrongly by using the builtin sum which does not take two arguments but an iterable. sum consumes the iterable in the first iteration of reduce and returns an int which is not further reduceable.
In the second case, sum is not even called on the iterable:
If initializer is not given and iterable contains only one item, the
first item is returned.
Since iterable is of length 1, reduce returns the one item in the iterable and sum does not nothing.
Input in second code snippet is single element list - it's a degenerated case where callable isn't even called.
Consider following example:
def my_sum(*args):
print "my_sum called with", args
return sum(*args)
x = [1, 2, 3]
reduce(my_sum, [b for b in x if b > 2])
# nothing gets printed, value 3 is returned, since it's only element in list
Now consider your failed case:
reduce(my_sum, [b for b in x if b > 0])
Output is:
my_sum called with (1, 2)
[exception traceback]
Equivalent call to builtin sum is sum(1, 2), which results in same exception as quoted by you.
sum signature does not follow rules for reduce function. sum(iterable[, start]) takes two arguments (as expected), but first of them has to be iterable, where second is optional initial value.
reduce requires function to take two arguments, where (quoting docs):
The left argument, x, is the accumulated value and the right argument,
y, is the update value from the iterable.
We can clearly see that these interfaces are not conform with each other. Correct callable will be something like:
def reduce_sum(accumulator, new_value):
return accumulator + new_value
reduce(reduce_sum, [b for b in x if b > 0])
As Moses points out "sum consumes the iterable in the first iteration"
so let's play with it
>>> sum(1,2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> sum([1,2])
3
>>> sum(x)
6
>>> sum([b for b in x if b >0])
6
>>> reduce(lambda x, y: x+y, x)
6
>>> reduce(lambda x, y: x+y, [b for b in x if b > 0])
6
>>> reduce(lambda x, y: x+y, [b for b in x if b > 1])
5
The reduce function takes in a function and an iterable.
It then maps each element of that iterable to the function.
What your first b > 0 function is doing is trying to run sum with 2 arguments. One argument being the accumulated total, and the other being an element of the input list, for example it would look like: sum(0, 1).
This is stated in the documentation as:
Apply function of two arguments cumulatively to the items of iterable, from left to right, so as to reduce the iterable to a single value. ... The left argument, is the accumulated value and the right argument, is the update value from the iterable.
What your second b > 2 function is doing is simply running the reduce function with an iterator containing one item, which does not attempt to apply sum as stated in the documentation.
If initializer is not given and iterable contains only one item, the first item is returned.
But since sum takes in an iterable, you could just do:
sum([b for b in x if b > 0])
Related
def mean(x):
return sum(x) / len(x)
my_list=[1,2,3,4,5]
v=list(map(mean,my_list))
when i run the code i face the following error:
return sum(x) / len(x)
TypeError: 'int' object is not iterable
To further explain some of what 35308 has mentioned in his comment, map takes each element from the list one-by-one and applies the mean function to it.
So, in your case, it will first assign x = 1, and then try to run mean on x.
When it does this, it will have sum(1)/len(1). Both sum and len require the argument being passed to it to be iterable, and an int is not an iterable.
If you just want the mean, just call mean on the list my_list, as the list is already iterable.
In Python3 I am Trying to figure out about reduce() and function as argument of function, or better passing a function as an argument of another one where the first one is not explicit see below
given:
# define a function `call` where you provide the function and the arguments
def call(y,f):
return f(y)
# define a function that returns the square
square = lambda x : x*x
# define a function that returns the increment
increment = lambda x : x+1
# define a function that returns the cube
cube = lambda x : x*x*x
# define a function that returns the decrement
decrement = lambda x : x-1
# put all the functions in a list in the order that you want to execute them
funcs = [square, increment, cube, decrement]
#bring it all together. Below is the non functional part.
#in functional programming you separate the functional and the non functional parts.
from functools import reduce # reduce is in the functools library
print(reduce(call, funcs,1)) # output 7 , 2 res 124
why it doesnt work if
I change
def call(y,f)
f(y)
in
def call(f,y)
f(y)
and gives out a error:
................py", line 27, in call
return f(y)
TypeError: 'int' object is not callable
functools.reduce()
To understand this, we should first understand how reduce works, reduce takes 3 arguments:
A function
An iterable element
An initializer.
Let us focus on the function and iterable element to understand how function is called
Below is the official docs of functools:
functools.reduce(function, iterable[, initializer])
Apply function of
two arguments cumulatively to the items of iterable, from left to
right, so as to reduce the iterable to a single value. For example,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). The left argument, x, is the accumulated value and
the right argument, y, is the update value from the iterable. If the
optional initializer is present, it is placed before the items of the
iterable in the calculation, and serves as a default when the iterable
is empty. If initializer is not given and iterable contains only one
item, the first item is returned.
Roughly equivalent to:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
Here you could understand that, it takes the function passed in the first argument and executes it with value, element as arguments for the function passed. Note that element is each element in the second argument iterable. So when you called reduce(call, funcs, 1),
The following happened:
Since initializer=1, value=initializer,
for every func in funcs, the following happened
call(1,func)
TLDR;
When you replace y and f, you are trying to call 1(func) which is not possible and that is why the first initial solution works because it calls func(1)
Reference: Python Docs - functools
I have long loops for deduping names, and I was hoping to simplify things by using a function rather than repeating all the carry-on effects of a match each time. I'm starting with the following test case, but it doesn't work like I expect:
x, y = 0, 0
def testFunc(*args):
global x, y
for arg in args:
if arg:
x +=1
else:
y +=1
return (x, y)
When I run it:
>>>testFunc(x==y,x==y,x==y)
(3,0)
>>>testFunc(x==y)
(3,1)
>>>testFunc(x!=y,x!=y,x!=y)
(3,4)
Basically, the arguments seem to be transformed into boolean before any operation happens. Is there a way to avoid that? I would have expected:
>>>testFunc(x==y,x==y,x==y)
(2,1)
>>>testFunc(x==y)
(2,2)
>>>testFunc(x!=y,x!=y,x!=y)
(4,3)
Basically, the arguments seem to be transformed into boolean before any operation happens.
Python first evaluates the arguments before calling the function, so this is the expected behaviour. The equality of two ints is a boolean. So it first evaluates x == y three times, each time yielding the same result. Next it calls the function with testFunc(True, True, True).
Is there a way to avoid that?
You can make it a callable, and thus postpone evaluation, with:
def testFunc(*args):
global x, y
for arg in args:
if arg(): # we call arg
x +=1
else:
y +=1
return (x, y)
and then calling it with:
>>> eq = lambda: x == y
>>> neq = lambda: x != y
>>> testFunc(eq, eq, eq)
(2, 1)
>>> testFunc(eq)
(2, 2)
>>> testFunc(neq, neq, neq)
(3, 4)
Here we thus do not pass the result of x == y, we pass a function that, when called, calculates the result of x == y. As a result the value is calculates for x and y at the moment when the call is made.
The statement x==y tests for equality, and will only yield a boolean. To assign your variable, just use one = sign.
For example:
1==3 yields False, therefore x==y will do the same unless x and y are explicitly the same
testFunc(x=y) should solve your problem. Furthermore, *args implies a list of arguments:
def myFunc(*args):
for arg in args:
print(arg)
Will take myFunc(*[1,2,3,4,5,6,7]) and print each member of that list. Note, you will need the *<list> syntax, otherwise myFunc(<list>) will have the list be the first and only argument. The * unpacks the values from the list
I am just learning generators in Python. It seems that if you assign generator to a tuple then next() is called silently behind the scenes - as if unpacking was forcing that call. But if you assign to tuple with single value then you get generator object itself. Do I get it right?
Trivial code follows:
def generator(n):
x = 0
while x < n:
yield x
x = x + 1
(x,*foo) = generator(1)
print(x, foo)
(x,*foo) = generator(3)
print(x, foo)
(x) = generator(1)
print(x)
Output is:
0 []
0 [1, 2]
<generator object generator at 0x05F06900>
The syntax
(x) = generator(1)
is not a tuple of one item. You want:
(x,) = generator(1)
or
x, = generator(1)
Then you'll find the generator is called, just as in your other examples, due to "unpacking".
Note that in the expression (x, y) it is not the () that make it a tuple, it is the comma. The parentheses simply bound the expression x, y.
The first two lines use tuple packing/unpacking: they yield all values from the generator and then set them to x and foo. This is because (x, foo) is syntax for a tuple (a pair of items).
However, (x) is syntax for a variable. You would get what you expected if you write (x,), which is how tuples of size 1 are created.
I'd like to learn how to pass an arbitrary number of args in a python function, so I wrote a simple sum function in a recursive way as follows:
def mySum(*args):
if len(args) == 1:
return args[0]
else:
return args[-1] + mySum(args[:-1])
but when I tested mySum(3, 4), I got this error:
TypeError: unsupported operand type(s) for +: 'int' and 'tuple'
Does anyone have an idea about this and gimme some clue to correct it?
This line:
return args[-1] + mySum(args[:-1])
args[:-1] returns a slice of the arguments tuple. I assume your goal is to recursively call your function using that slice of the arguments. Unfortunately, your current code simply calls your function using a single object - the slice itself.
What you want to do instead is to call with those args unrolled.
return args[-1] + mySum(*args[:-1])
^---- note the asterisk
This technique is called "unpacking argument lists," and the asterisk is sometimes (informally) called the "splat" operator.
If you don't want to do it recursively:
def mySum(*args):
sum = 0
for i in args:
sum = sum + i
return sum
args[:-1] is a tuple, so the nested call is actually mySum((4,)), and the nested return of args[0] returns a tuple. So you end up with the last line being reduced to return 3 + (4,). To fix this you need to expand the tuple when calling mySum by changing the last line to return args[-1] + mySum(*args[:-1]).
In your code, args[:-1] is a tuple, so mySum(args[:-1]) is being called with the args being a tuple containing another tuple as the first argument. You want to call the function mySum with args[:-1] expanded to the arguments however, which you can do with
mySum(*args[:-1])
The arbitrary arguments are passed as tuple (with one asterisk*) to the function, (you can change it to a list as shown in the code) and calculate the sum of its elements, by coding yourself using a for loop; if don't want to use the sum() method of python.
def summing(*arg):
li = list(*arg)
x = 0
for i in range((len(li)-1)):
x = li[i]+x
return x
#creating a list and pass it as arbitrary argument to the function
#to colculate the sum of it's elements
li = [4, 5 ,3, 24, 6, 67, 1]
print summing(li)
Option1:
def mySum(*args):
return sum(args)
mySum(1,2,3) # 6
mySum(1,2) # 3
Option 2:
mySum2 = lambda *args: sum(args)
mySum2(1,2,3) # 6
mySum2(1,2) # 3