Use of yield with a dict comprehension

2019-03-17 13:02发布

问题:

As a contrived example:

myset = set(['a', 'b', 'c', 'd'])
mydict = {item: (yield ''.join([item, 's'])) for item in myset}

and list(mydict) gives:

['as', 'cs', 'bs', 'ds', {'a': None, 'b': None, 'c': None, 'd': None}]

What happens here? What does yield do? And is this behavior consistent no matter what expression follows yield?

Note: I know that doing mydict = {item: ''.join([item, 's']) for item in myset} would give the dictionary {'a': 'as', 'b': 'bs', 'c': 'cs', 'd': 'ds'}, which seems to be what I am trying to do here.

回答1:

First of all, what does yield return? The answer in this case is None, because yield returns the parameter passed to next(), which is nothing in this case (list doesn't pass anything to next).

Now here's your answer:

>>> myset = set(['a', 'b', 'c', 'd'])
>>> mydict = {item: (yield ''.join([item, 's'])) for item in myset}
>>> mydict
<generator object <dictcomp> at 0x0222BB20>

The dict comprehension is turned into a generator, because you used yield in a function body context! This means that the whole thing isn't evaluated until it's passed into list.

So here's what happens:

  1. list calls next(mydict).
  2. Yield returns ''.join([item, 's']) to list and freezes the comprehension.
  3. list calls next(mydict).
  4. The comprehension resumes and assigns the result of yield (None) to item in the dictionary and starts a new comprehension iteration.
  5. Go back to 1.

And at last the actual generator object returns the temporary in the body, which was the dict. Why this happens is unknown to me, and it's probably not documented behaviour either.



回答2:

I think that yield is turning your nice dictionary comprehension into a generator expression. So, when you're iterating over the generator, yield is "yielding" the elements that look as, bs ..., but the statement yield ... returns None. So, at the end of the day, you get a dictionary looking like {'a': None, 'b': None, ...}.

The part that confuses me is why the dictionary is actually yielded at the end. I'm guessing that this behavior is actually not well defined by the standard, but I could be wrong about that.

Interestingly enough, if you try this with a list comprehension, python complains:

>>> a = [(yield i) for i in myset]
  File "<stdin>", line 1
SyntaxError: 'yield' outside function

But it's Ok in a generator (apparently).



回答3:

I find! ^_^

In normal life, expression

print {item: (yield ''.join([item, 's'])) for item in myset} 

evaluate like this:

def d(myset):
    result = {}
    for item in myset:
        result[item] = (''.join([item, 's']))
    yield result

print d(myset).next()

Why yield result instead return result? I think it is necessary to support nested list comprehensions* like this:

print {i: f.lower() for i in nums for f in fruit}  # yes, it's works

So, would look like this code?

def d(myset):
    result = {}
    for item in myset:
        result[item] = (yield ''.join([item, 's']))
    yield result

and

>>> print list(d(myset))
['as', 'cs', 'bs', 'ds', {'a': None, 'b': None, 'c': None, 'd': None}]

First will be returned all values of ''.join([item, 's']) and the last will be returned dict result. Value of yield expression is None, so values in the result is None too.

* More correct interpretation of evaluate nested list comprehensions:

print {i: f.lower() for i in nums for f in fruit}

# eval like this:

result = {}
for i, f in product(nums, fruit): # product from itertools
    key, value = (i, f.lower())
    result[key] = value
print result


回答4:

I think that your code has to execute similarity of this:

def d(myset):
    for item in myset:
        yield item, (yield ''.join([item, 's']))

d(myset)

Firstly, evaluated yield ''.join([item, 's'] (and return 'as', 'cs', etc.). Value of yield expression is None, because is sent back to the generator. And then eval yield item, None, that return tuples ('a', None), ('b', None).

So, I have:

>>> list(d(myset))
['as', ('a', None), 'cs', ('c', None), 'bs', ('b', None), 'ds', ('d', None)]

What happens next, I do not understand.