Примітка : це була помилка в обробці CPython yield
в розуміннях та виразах генератора, виправлена в Python 3.8, із попередженням про припинення використання в Python 3.7. Див. Звіт про помилки Python та записи Нові для Python 3.7 та Python 3.8 .
Вирази генератора, а також розуміння множин і диктів компілюються до об'єктів функції (генератора). У Python 3 розуміння списку отримує однакову обробку; всі вони, по суті, є новим вкладеним обсягом.
Ви можете переконатися в цьому, якщо спробувати розібрати вираз генератора:
>>> dis.dis(compile("(i for i in range(3))", '', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>)
3 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
9 LOAD_NAME 0 (range)
12 LOAD_CONST 2 (3)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 LOAD_CONST 3 (None)
26 RETURN_VALUE
>>> dis.dis(compile("(i for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 11 (to 17)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 POP_TOP
14 JUMP_ABSOLUTE 3
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
Вищезазначене показує, що вираз генератора компілюється до об'єкта коду, завантаженого як функція ( MAKE_FUNCTION
створює об'єкт функції з об'єкта коду). .co_consts[0]
Посилання дозволяє нам побачити об'єкт коду , згенерованого для вираження, і він використовує YIELD_VALUE
тільки як функцію генератора буде.
Таким чином, yield
вираз працює в цьому контексті, оскільки компілятор розглядає їх як маскувальні функції.
Це помилка; yield
не має місця в цих виразах. Граматика Python перед Python 3.7 це дозволяє (саме тому код можна компілювати), але yield
специфікація виразу показує, що використання yield
тут насправді не повинно працювати:
Вираз yield використовується лише при визначенні функції генератора і, отже, може використовуватися лише в тілі визначення функції.
Це було підтверджено помилкою у випуску 10544 . Дозвіл помилки в тому , що з допомогою yield
і yield from
буде підняти SyntaxError
в Python 3.8 ; в Python 3.7 він піднімає a,DeprecationWarning
щоб переконатися, що код припиняє використання цієї конструкції. Ви побачите те саме попередження в Python 2.7.15 та новіших версіях, якщо ви використовуєте -3
перемикач командного рядка, що вмикає попередження про сумісність Python 3.
Попередження 3.7.0b1 виглядає так; перетворення попереджень на помилки дає SyntaxError
виняток, як у 3.8:
>>> [(yield i) for i in range(3)]
<stdin>:1: DeprecationWarning: 'yield' inside list comprehension
<generator object <listcomp> at 0x1092ec7c8>
>>> import warnings
>>> warnings.simplefilter('error')
>>> [(yield i) for i in range(3)]
File "<stdin>", line 1
SyntaxError: 'yield' inside list comprehension
Відмінності між тим, як yield
функціонують розуміння списку та yield
генераторський вираз, походять із різниці в тому, як реалізовані ці два вирази. У Python 3 розуміння списку використовує LIST_APPEND
виклики, щоб додати верх стека до побудованого списку, тоді як вираз генератора натомість дає це значення. Додавання (yield <expr>)
просто додає ще один YIELD_VALUE
код операції до будь-якого:
>>> dis.dis(compile("[(yield i) for i in range(3)]", '', 'exec').co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 13 (to 22)
9 STORE_FAST 1 (i)
12 LOAD_FAST 1 (i)
15 YIELD_VALUE
16 LIST_APPEND 2
19 JUMP_ABSOLUTE 6
>> 22 RETURN_VALUE
>>> dis.dis(compile("((yield i) for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 YIELD_VALUE
14 POP_TOP
15 JUMP_ABSOLUTE 3
>> 18 LOAD_CONST 0 (None)
21 RETURN_VALUE
YIELD_VALUE
Опкод в байткод індексів 15 і 12 , відповідно , є додатковою, зозуля в гнізді. Отже, для генератора списку-розуміння, що перетворився, у вас є 1 вихід, який кожного разу створює верх стека (замінюючи верх стека на yield
значення, що повертається), а для варіанта виразу генератора ви отримуєте верх стека ( ціле число), а потім знову приносять результат , але тепер стек містить повернене значення, yield
і ви отримуєте None
це вдруге.
Для розуміння списку тоді передбачуваний list
вихідний об'єкт все ще повертається, але Python 3 розглядає це як генератор, тому натомість повернене значення приєднується до StopIteration
винятку як value
атрибут:
>>> from itertools import islice
>>> listgen = [(yield i) for i in range(3)]
>>> list(islice(listgen, 3))
[0, 1, 2]
>>> try:
... next(listgen)
... except StopIteration as si:
... print(si.value)
...
[None, None, None]
Ці None
об'єкти - це повернені значення з yield
виразів.
І ще раз повторити це; це саме питання стосується також розуміння словника та набору в Python 2 та Python 3; у Python 2 yield
повернені значення все ще додаються до передбачуваного словника або об'єкта набору, і повертане значення остаточно `` видається '', а не приєднується до StopIteration
винятку:
>>> list({(yield k): (yield v) for k, v in {'foo': 'bar', 'spam': 'eggs'}.items()})
['bar', 'foo', 'eggs', 'spam', {None: None}]
>>> list({(yield i) for i in range(3)})
[0, 1, 2, set([None])]
yield-atom
це дозволено всередині виразу (всередині функції генератора). Це може бути ще більш проблематичним, якщо йогоyield-atom
якось неправильно реалізувати.