I need your help, because I don't understand why is possible to use the join() method with a forloop as an argument.
Ex:
" ".join(str(x) for x in list)
Python Documentation:
str.join(iterable)
Return a string which is the concatenation of the strings in iterable.
A TypeError will be raised if there are any non-string values in iterable, including bytes objects. The separator between elements is the string providing this method.
Can please somebody explain?
The statement (str(x) for x in list) is called a generator expression:
>>> (str(x) for x in [1,2,3])
<generator object <genexpr> at 0x7fc916f01d20>
What this does is create an object that can be iterated over exactly once, and yields elements that would be created one at a time. You can iterate over it as you would a list, like this:
>>> gen = (str(x) for x in [1,2,3])
>>> for s in gen:
... print s
...
1
2
3
A generator expression is iterable, so what the join function does is iterate over it and join its values.
Related
How come I can call next to a reversed range but can't call it on a regular range ?
r1 = range(6)
next(r1) # Error
r2 = reversed(range(6))
next(r2) # -> 5
There is a subtle distinction here. First, range is a type. An instance of range is not an iterator, because range.__next__ is not defined. An instance is iterable, though, because range.__iter__ is defined, so you can get an iterator with, for example, iter(range(3)).
>>> type(range(1))
<class 'range'>
>>> type(iter(range(1)))
<class 'range_iterator'>
range.__next__ is not defined, but range_iterator.__next__ is.
An instance of range represents a bounded sequence of integers, without actually being a bounded sequences. As such, you can have multiple independent iterators over the same range.
>>> r = range(10)
>>> i1 = iter(r)
>>> next(i1)
0
>>> next(i1)
1
>>> next(i1)
2
>>> i2 = iter(r)
>>> next(i2)
0
>>> next(i1)
3
reversed, however, by definition returns an iterator. If need be, it can call iter on its iterable argument in order to get an iterator to reverse. It can also use its argument's __reversed__ method to get a reverse iterator. range.__reversed__ yields an iterator like range.__iter__, but going in the opposite direction.
Because, per the reversed() docs:
Return a reverse iterator.
range(), however, returns an immutable sequence.
next() can only be used on objects with a __next__() method.
primes = [2,3,5,7..] (prime numbers)
map(lambda x:print(x),primes)
It does not print anything.
Why is that?
I've tried
sys.stdout.write(x)
too, but doesn't work either.
Since lambda x: print(x) is a syntax error in Python < 3, I'm assuming Python 3. That means map returns a generator, meaning to get map to actually call the function on every element of a list, you need to iterate through the resultant generator.
Fortunately, this can be done easily:
list(map(lambda x:print(x),primes))
Oh, and you can get rid of the lambda too, if you like:
list(map(print,primes))
But, at that point you are better off with letting print handle it:
print(*primes, sep='\n')
NOTE: I said earlier that '\n'.join would be a good idea. That is only true for a list of str's.
This works for me:
>>> from __future__ import print_function
>>> map(lambda x: print(x), primes)
2
3
5
7
17: [None, None, None, None]
Are you using Python 2.x where print is a statement, not a function?
Alternatively, you can unpack it by putting * before map(...) like the following
[*map(...)]
or
{*map(...)}
Choose the output you desire, a list or a dictionary.
Another reason why you could be seeing this is that you're not evaluating the results of the map function. It returns a generator (an iterable) that evaluates your function lazily and not an actual list.
primes = [2,3,5,7]
map(print, primes) # no output, because it returns a generator
primes = [2,3,5,7]
for i in map(print, primes):
pass # prints 2,3,5,7
Alternately, you can do list(map(print, primes)) which will also force the generator to be evaluated and call the print function on each member of your list.
I am wondering if there is there is a simple Pythonic way (maybe using generators) to run a function over each item in a list and result in a list of returns?
Example:
def square_it(x):
return x*x
x_set = [0,1,2,3,4]
squared_set = square_it(x for x in x_set)
I notice that when I do a line by line debug on this, the object that gets passed into the function is a generator.
Because of this, I get an error:
TypeError: unsupported operand type(s) for *: 'generator' and 'generator'
I understand that this generator expression created a generator to be passed into the function, but I am wondering if there is a cool way to accomplish running the function multiple times only by specifying an iterable as the argument? (without modifying the function to expect an iterable).
It seems to me that this ability would be really useful to cut down on lines of code because you would not need to create a loop to fun the function and a variable to save the output in a list.
Thanks!
You want a list comprehension:
squared_set = [square_it(x) for x in x_set]
There's a builtin function, map(), for this common problem.
>>> map(square_it, x_set)
[0,1,4,9,16] # On Python 3, a generator is returned.
Alternatively, one can use a generator expression, which is memory-efficient but lazy (meaning the values will not be computed now, only when needed):
>>> (square_it(x) for x in x_set)
<generator object <genexpr> at ...>
Similarly, one can also use a list comprehension, which computes all the values upon creation, returning a list.
Additionally, here's a comparison of generator expressions and list comprehensions.
You want to call the square_it function inside the generator, not on the generator.
squared_set = (square_it(x) for x in x_set)
As the other answers have suggested, I think it is best (most "pythonic") to call your function explicitly on each element, using a list or generator comprehension.
To actually answer the question though, you can wrap your function that operates over scalers with a function that sniffs the input, and has different behavior depending on what it sees. For example:
>>> import types
>>> def scaler_over_generator(f):
... def wrapper(x):
... if isinstance(x, types.GeneratorType):
... return [f(i) for i in x]
... return f(x)
... return wrapper
>>> def square_it(x):
... return x * x
>>> square_it_maybe_over = scaler_over_generator(square_it)
>>> square_it_maybe_over(10)
100
>>> square_it_maybe_over(x for x in range(5))
[0, 1, 4, 9, 16]
I wouldn't use this idiom in my code, but it is possible to do.
You could also code it up with a decorator, like so:
>>> #scaler_over_generator
... def square_it(x):
... return x * x
>>> square_it(x for x in range(5))
[0, 1, 4, 9, 16]
If you didn't want/need a handle to the original function.
Note that there is a difference between list comprehension returning a list
squared_set = [square_it(x) for x in x_set]
and returning a generator that you can iterate over it:
squared_set = (square_it(x) for x in x_set)
I have this:
>>> sum( i*i for i in xrange(5))
My question is, in this case am I passing a list comprehension or a generator object to sum ? How do I tell that? Is there a general rule around this?
Also remember sum by itself needs a pair of parentheses to surround its arguments. I'd think that the parentheses above are for sum and not for creating a generator object. Wouldn't you agree?
You are passing in a generator expression.
A list comprehension is specified with square brackets ([...]). A list comprehension builds a list object first, so it uses syntax closely related to the list literal syntax:
list_literal = [1, 2, 3]
list_comprehension = [i for i in range(4) if i > 0]
A generator expression, on the other hand, creates an iterator object. Only when iterating over that object is the contained loop executed and are items produced. The generator expression does not retain those items; there is no list object being built.
A generator expression always uses (...) round parethesis, but when used as the only argument to a call, the parenthesis can be omitted; the following two expressions are equivalent:
sum((i*i for i in xrange(5))) # with parenthesis
sum(i*i for i in xrange(5)) # without parenthesis around the generator
Quoting from the generator expression documentation:
The parentheses can be omitted on calls with only one argument. See section Calls for the detail.
List comprehensions are enclosed in []:
>>> [i*i for i in xrange(5)] # list comprehension
[0, 1, 4, 9, 16]
>>> (i*i for i in xrange(5)) # generator
<generator object <genexpr> at 0x2cee40>
You are passing a generator.
That is a generator:
>>> (i*i for i in xrange(5))
<generator object <genexpr> at 0x01A27A08>
>>>
List comprehensions are enclosed in [].
You might also be asking, "does this syntax truly cause sum to consume a generator one item at a time, or does it secretly create a list of every item in the generator first"? One way to check this is to try it on a very large range and watch memory usage:
sum(i for i in xrange(int(1e8)))
Memory usage for this case is constant, where as range(int(1e8)) creates the full list and consumes several hundred MB of RAM.
You can test that the parentheses are optional:
def print_it(obj):
print obj
print_it(i for i in xrange(5))
# prints <generator object <genexpr> at 0x03853C60>
I tried this:
#!/usr/bin/env python
class myclass:
def __init__(self,arg):
self.p = arg
print type(self.p)
print self.p
if __name__ == '__main__':
c = myclass(i*i for i in xrange(5))
And this prints:
$ ./genexprorlistcomp.py
<type 'generator'>
<generator object <genexpr> at 0x7f5344c7cf00>
Which is consistent with what Martin and mdscruggs explained in their post.
You are passing a generator object, list comprehension is surrounded by [].
Basically my question is say you have an list containing 'None' how would you try retrieving the sum of the list. Below is an example I tried which doesn't work and I get the error: TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'. Thanks
def sumImport(self):
my_list = [[1,2,3,None],[1,2,3],[1,1],[1,1,2,2]]
k = sum(chain.from_iterable(my_list))
return k
You can use filter function
>>> sum(filter(None, [1,2,3,None]))
6
Updated from comments
Typically filter usage is filter(func, iterable), but passing None as first argument is a special case, described in Python docs. Quoting:
If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.
Remove None (and zero) elements before summing by using filter:
>>> k = sum(filter(None, chain.from_iterable(my_list)))
>>> k
20
To see why this works, see the documentation for filter:
filter(function, iterable)
Construct a list from those elements of iterable for which function returns true. iterable may be either a sequence, a container which supports iteration, or an iterator. If iterable is a string or a tuple, the result also has that type; otherwise it is always a list. If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.
Note that filter(function, iterable) is equivalent to [item for item in iterable if function(item)] if function is not None and [item for item in iterable if item] if function is None.
Another suggestion:
from itertools import chain
k = sum(x for x in chain.from_iterable(my_list) if x)
Assuming you want to treat None as zero, a simple way is
sum(x if x is not None else 0 for x in chain.from_iterable(my_list))
Explicitly, this is equivalent to filter:
k = sum([x for x in chain.from_iterable(my_list) if x])
That saves me from remembering another function. :P
You always have the option of just writing the loop you want:
k = 0
for sublist in my_list:
for val in sublist:
if val is not None:
k += val
But it certainly doesn’t hurt to know about filter either.
Just using sum and map:
sum(map(lambda x: x or 0, [1,2,3,None]))
# 6