Подписчиков события звонят в порядке подписки?

Примечание: это была ошибка в обработке 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])]
25
задан EricSchaefer 17 December 2008 в 18:31
поделиться

4 ответа

Учитывая, что реализация, да, их будут всегда называть в том порядке.

, Если событие на самом деле использует некоторый странный и замечательный способ обработать подписки, это могло бы сделать разные вещи - но "нормальные" реализации сделают правильную вещь.

, Чтобы быть ясным, подписываясь на обработчик событий просто означает вызывать соответствующее, "добавляет" часть события. Если событие обрабатывает это путем выполнения чего-то как:

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);
}

затем обработчики назвали бы в обратном порядке.

Сводка : Для всех нормальных событий можно полагаться на упорядочивание. В теории события могут сделать то, что они любят, но я никогда не видел событие, которое не делает , поддерживают соответствующее упорядочивание.

41
ответ дан Patrick Desjardins 15 October 2019 в 15:03
поделиться

Обратите очень пристальное внимание на протесты, данные Jon Skeet - "Учитывая, что реализация...". Другими словами, внесите малейшее изменение (несколько потоков, другие обработчики, и т.д.), и Вы рискуете терять инвариантность порядка выполнения.

Делают НЕ , полагаются на событие, заказывая . Все отправки события должны быть логически независимыми, как будто они происходили параллельно. События являются логически независимыми действиями.

я буду идти один шаг вперед и утверждать, что, если необходимо принять порядок на увольнение событий, Вы имеете серьезный недостаток дизайна и/или неправильно используете события.

23
ответ дан Steven A. Lowe 15 October 2019 в 15:03
поделиться

Быстрый ответ был бы, "Это не Ваше дело" :)

событие является асинхронным по своей природе. Это означает, что Вы не ожидаете события, которое будет запущено, или ожидаете, что это произойдет в установленный срок. Они просто происходят, и затем Вы принимаете меры. Желание знать, 'когда' или пытающийся выяснить, 'как' собирается повредить эту природу.

, Возможно, в этом случае Вам не нужен основанный на событии подход для добиваний цели?

то, Что сказал Jon Skeet, технически корректно для текущей реализации, но возможно она не будет в c#8.5 или VBasic 15.0. Доверие деталям реализации всегда собирается принести больше вреда, чем пользы.

4
ответ дан Steven A. Lowe 15 October 2019 в 15:03
поделиться

Даже если бы их называют в правильном порядке, я попытался бы не написать код, который полагается на предыдущего делегата, уволенного за него для функционирования правильно.

Если Два () иждивенец на чем-то, что Один () делает, затем или присоедините единственного делегата, который называет эти два метода в правильном порядке или имеет Два (), вызывают One () при необходимости.

11
ответ дан Paul 28 November 2019 в 17:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: