views:

185

answers:

4

Is there a more concise, efficient or simply pythonic way to do the following?

def product(list):
    p = 1
    for i in list:
        p *= i
    return p

EDIT:

I actually find that this is marginally faster than using operator.mul:

from operator import mul
# from functools import reduce # python3 compatibility

def with_lambda(list):
    reduce(lambda x, y: x * y, list)

def without_lambda(list):
    reduce(mul, list)

def forloop(list):
    r = 1
    for x in list:
        r *= x
    return r

import timeit

a = range(50)
b = range(1,50)#no zero
t = timeit.Timer("with_lambda(a)", "from __main__ import with_lambda,a")
print("with lambda:", t.timeit())
t = timeit.Timer("without_lambda(a)", "from __main__ import without_lambda,a")
print("without lambda:", t.timeit())
t = timeit.Timer("forloop(a)", "from __main__ import forloop,a")
print("for loop:", t.timeit())

t = timeit.Timer("with_lambda(b)", "from __main__ import with_lambda,b")
print("with lambda (no 0):", t.timeit())
t = timeit.Timer("without_lambda(b)", "from __main__ import without_lambda,b")
print("without lambda (no 0):", t.timeit())
t = timeit.Timer("forloop(b)", "from __main__ import forloop,b")
print("for loop (no 0):", t.timeit())

gives me

('with lambda:', 17.755449056625366)
('without lambda:', 8.2084708213806152)
('for loop:', 7.4836349487304688)
('with lambda (no 0):', 22.570688009262085)
('without lambda (no 0):', 12.472226858139038)
('for loop (no 0):', 11.04065990447998)
+12  A: 
reduce(lambda x, y: x * y, list)
jellybean
+1 but see @wiso's answer about `operator.mul` for a better way to do it.
Chris Lutz
+11  A: 

Without using lambda:


from operator import mul
reduce(mul, list)

it's better and faster:


from operator import mul
# from functools import reduce # python3 compatibility

def with_lambda(list):
    reduce(lambda x, y: x * y, list)

def without_lambda(list):
    reduce(mul, list)

import timeit

a = range(100)
t = timeit.Timer("with_lambda(a)", "from __main__ import with_lambda,a")
print("with lambda:", t.timeit())
t = timeit.Timer("without_lambda(a)", "from __main__ import without_lambda,a")
print("without lambda:", t.timeit())

python 2.6:

('with lambda:', 26.394401073455811)
('without lambda:', 10.088989019393921)
python 3.0:
with lambda: 34.9788780212
without lambda: 11.819715023
Python 3.0 slower (?)

wiso
Very interesting, thanks. Any idea why python 3 might be slower?
Simon Watkins
No, can someone confirm that?
wiso
@wiso: Possible reasons: (1) Python 3 `int` is Python 2 `long`. Python 2 will be using "int" until it overflows 32 bits; Python 3 will use "long" from the start. (2) Python 3.0 was a "proof of concept". Upgrade to 3.1 ASAP!
John Machin
I've redone the same test on an other machine:python 2.6('with lambda:', 21.843887090682983) ('without lambda:', 9.7096879482269287)python 3.1:with lambda: 24.7712180614without lambda: 10.7758350372
wiso
A: 
import operator
reduce(operator.mul, list, 1)
Ian Clelland
is the last argument (1) really necessary?
wiso
The last argument is necessary if the list may be empty, otherwise it will throw a TypeError exception. Of course, sometimes an exception will be what you want.
Dave Kirby
+1  A: 

I remember some long discussions on comp.lang.python (sorry, too lazy to produce pointers now) which concluded that your original product() definition is the most Pythonic.

Note that the proposal is not to write a for loop every time you want to do it, but to write a function once (per type of reduction) and call it as needed! Calling reduction functions is very Pythonic - it works sweetly with generator expressions, and since the sucessful introduction of sum(), Python keeps growing more and more builtin reduction functions - any() and all() are the latest additions...

This conclusion is kinda official - reduce() was removed from builtins in Python 3.0, saying:

"Use functools.reduce() if you really need it; however, 99 percent of the time an explicit for loop is more readable."

See also The fate of reduce() in Python 3000 for a supporting quote from Guido (and some less supporting comments by Lispers that read that blog).

P.S. if by chance you need product() for combinatorics, see math.factorial() (new 2.6).

Beni Cherniavsky-Paskin
+1 for an accurate (to the best of my knowledge) account of the prevailing moods in the Python community -- while I definately prefer going against said prevailing moods in this case, it's best to know them for what they are anyway. Also, I like the bit about unsupportive Lispers from LtU (I'd be one of those, I guess). :-)
Michał Marczyk