Примечание: это была ошибка в обработке CPython для
yield
в выражениях выражений и выражений генератора, исправленных в Python 3.8, с предупреждением об отказе в Python 3.7. Смотрите отчет об ошибках Python и записи What's New для Python 3.7 и Python 3.8 .Выражения генератора, а также функции set и dict компилируются в объекты функции (генератора). В Python 3 переписные справки получают одинаковое обращение; все они, по существу, представляют собой новую вложенную область.
Вы можете увидеть это, если попытаетесь разобрать выражение генератора:
>>> dis.dis(compile("(i for i in range(3))", '', 'exec')) 1 0 LOAD_CONST 0 (
at 0x10f7530c0, file "", line 1>) 3 LOAD_CONST 1 ('
') 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 используется только при определении функции generator и, следовательно, может использоваться только в теле определения функции.
blockquote >Это было подтверждено как ошибка в выпуске 10544 . Разрешение ошибки заключается в том, что с помощью
yield
иyield from
будет поднятьSyntaxError
в Python 3.8 ; в Python 3.7 он вызываетDeprecationWarning
, чтобы гарантировать, что код перестает использовать эту конструкцию. Вы увидите то же предупреждение в Python 2.7.15 и выше, если вы используете переключатель командной строки-3
, включающий предупреждения о совместимости с Python 3.Появляется предупреждение 3.7.0b1 как это; включение предупреждений в ошибки дает вам исключение
SyntaxError
, как и в 3.8:>>> [(yield i) for i in range(3)]
:1: DeprecationWarning: 'yield' inside list comprehension at 0x1092ec7c8> >>> import warnings >>> warnings.simplefilter('error') >>> [(yield i) for i in range(3)] File " ", line 1 SyntaxError: 'yield' inside list comprehension . Различия между тем, как
yield
в понимании списка иyield
в выражении генератора работают, вытекают из различия в том, как эти два выражения реализованы. В Python 3 в понимании списка используются вызовыLIST_APPEND
, чтобы добавить верхнюю часть стека в построенный список, в то время как выражение генератора вместо этого дает это значение. Добавление в(yield
просто добавляет еще один код операции) 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
), а для варианта выражения генератора вы получаете верхнюю часть stack (целое число), а затем снова , но теперь стек содержит возвращаемое значениеyield
, и вы получитеNone
второй раз.Для списка тогда предполагается, что предполагаемый вывод объекта
list
все еще возвращен, но Python 3 рассматривает это как генератор, поэтому вместо этого значения возвратаStopIteration
в качестве атрибутаvalue
добавляется возвращаемое значение:>>> from itertools import islice >>> listgen = [(yield i) for i in range(3)] >>> list(islice(listgen, 3)) # avoid exhausting the generator [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])]
Учитывая, что реализация, да, их будут всегда называть в том порядке.
, Если событие на самом деле использует некоторый странный и замечательный способ обработать подписки, это могло бы сделать разные вещи - но "нормальные" реализации сделают правильную вещь.
, Чтобы быть ясным, подписываясь на обработчик событий просто означает вызывать соответствующее, "добавляет" часть события. Если событие обрабатывает это путем выполнения чего-то как:
myHandler += value;
, который переводится в
myHandler = Delegate.Combine(myHandler, value);
и Делегат. Объединение гарантии упорядочивание. Однако, если у Вас было событие как это:
private LinkedList<EventHandler> eventHandlers = new LinkedList<EventHandler>;
public event EventHandler Foo
{
add
{
eventHandlers.AddFirst(value);
}
remove
{
// do stuff here too
}
}
и затем запущенный событие путем выполнения чего-то как:
foreach (EventHandler handler in eventHandlers)
{
handler(this, EventArgs.Empty);
}
затем обработчики назвали бы в обратном порядке.
Сводка : Для всех нормальных событий можно полагаться на упорядочивание. В теории события могут сделать то, что они любят, но я никогда не видел событие, которое не делает , поддерживают соответствующее упорядочивание.
Обратите очень пристальное внимание на протесты, данные Jon Skeet - "Учитывая, что реализация...". Другими словами, внесите малейшее изменение (несколько потоков, другие обработчики, и т.д.), и Вы рискуете терять инвариантность порядка выполнения.
Делают НЕ , полагаются на событие, заказывая . Все отправки события должны быть логически независимыми, как будто они происходили параллельно. События являются логически независимыми действиями.
я буду идти один шаг вперед и утверждать, что, если необходимо принять порядок на увольнение событий, Вы имеете серьезный недостаток дизайна и/или неправильно используете события.
Быстрый ответ был бы, "Это не Ваше дело" :)
событие является асинхронным по своей природе. Это означает, что Вы не ожидаете события, которое будет запущено, или ожидаете, что это произойдет в установленный срок. Они просто происходят, и затем Вы принимаете меры. Желание знать, 'когда' или пытающийся выяснить, 'как' собирается повредить эту природу.
, Возможно, в этом случае Вам не нужен основанный на событии подход для добиваний цели?
то, Что сказал Jon Skeet, технически корректно для текущей реализации, но возможно она не будет в c#8.5 или VBasic 15.0. Доверие деталям реализации всегда собирается принести больше вреда, чем пользы.
Даже если бы их называют в правильном порядке, я попытался бы не написать код, который полагается на предыдущего делегата, уволенного за него для функционирования правильно.
Если Два () иждивенец на чем-то, что Один () делает, затем или присоедините единственного делегата, который называет эти два метода в правильном порядке или имеет Два (), вызывают One () при необходимости.