6

I'm trying to implement my own version of itertools.compress, the problem is that i stumbled upon the return type. I mean both of these functions return an iterator, but i think the second one is not considered a generator function because there is no yield statement inside. So my question is, are these two implementations equivalent ?

def compress (seq, selectors):
    from operator import itemgetter
    fst = itemgetter (0)
    snd = itemgetter (1)
    yield from map (fst, filter (snd, zip (seq, selectors)))

def compress (seq, selectors):
    from operator import itemgetter
    fst = itemgetter (0)
    snd = itemgetter (1)
    return map (fst, filter (snd, zip (seq, selectors)))
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
marsouf
  • 1,107
  • 8
  • 15

3 Answers3

4

Not quite.

yield from seq is equivalent to for i in seq: yield i

This means your first implementation is a generator that yields each item from the result of map(), while your second implementation returns the map object.

avayert
  • 664
  • 5
  • 9
  • 2
    This answer is basically repeating what has been said in the question, without explaining actual differences between the two versions! – Stef Mar 18 '22 at 14:49
4

So my question is, are these two implementations equivalent ?

Your two implementations are not technically equivalent, since they both return different kinds of iterator objects, but they are functionality equivalent, since the resulting objects they return will behave the same as an itertools.compress object.

yield from and return are two different syntactic constructs and thus technically have two different semantic meanings:

yield from <iter> is equivalent to for element in <iter>: yield element, which means when you call your function, it will return a generator, so each subsequent next call on the generator will yield another element from <iter>.

return <expr> on the other hand, will simply return the <expr> object, unchanged, and the function execution will end at the return statement.

In your case, both are actually functionally equivalent to itertools.compress, since in the first case a generator object is returned, which is functionally equivalent to an itertools.compress object, and in the second case a map iterator is returned, which is also functionally equivalent to an itertools.compress object.

So either option, purely functionally speaking would work as custom implementations of itertools.compress. In terms of clarity and conciseness, I would prefer the second version, as the yield from is superfluous, and you get the same functionality just by returning the map iterator itself.

Christian Dean
  • 22,138
  • 7
  • 54
  • 87
  • 1
    Question is about returning an iterator, so the statement "`return` will simply return a single value" is only a technicality. From the outside view both will be equivalent. – cube Jul 27 '18 at 05:51
  • I suppose strictly in terms of functionality @cube. But there is still a distinction. The `yield from` version of `compress` returns a generator object, whereas the `return` version of `compress` returns an iterator object. And as @Wondercricket noted below, using `yield from` is the more efficient option. So at least in this case I still think it's important to make clear there is a distinction in the two options presented. – Christian Dean Jul 28 '18 at 14:02
  • 1
    This answer basically states "`yield from` and return are two different keywords", which the question already acknowledged. The question is: what is the effective difference between using one or using the other? Your answer doesn't explain that. Then you conclude "You want to yield the values from the map iterator, not return the iterator itself." without any justification. – Stef Mar 18 '22 at 14:55
  • @Stef All correct statements. I wrote this answer five years ago when I was still a young teenager and knew very little about how Python actually worked under the hood, so I appreciate you bringing this old answer to my attention. I've edited the answer in an attempt to improve its quality. Thanks! – Christian Dean Mar 18 '22 at 16:01
0

I know it is an old thread, but I found a case where it actually makes a difference.

Since return well returns from the function, all blocks are also terminated. Especially all currently active with-blocks. This will also close any opened files.

So in the following case, a call to iterate_lines will throw an Error, because the file was already closed:

def read_lines(f):
    for line in f:
        yield line

def iterate_lines():
    with open("file.txt", "r") as f:
        return read_lines(f)

But if we use yield from, it works:

def read_lines(f):
    for line in f:
        yield line

def iterate_lines():
    with open("file.txt", "r") as f:
        yield from read_lines(f)
Syn
  • 1