Модуль blinker
обеспечивает быструю и простую передачу сигналов между объектами и широковещательную передачу сигналов для объектов Python.
Ядро модуля blinker
довольно маленькое, но обладает мощными функциями:
blinker
в виртуальное окружение;blinker
;Signal.connect()
- подписка на сигналы blinker
;Signal.send()
- отправка сигналов blinker
;blinker
;blinker
;Signal.connect
в качестве декоратора;blinker
.blinker
в виртуальное окружение.# создаем виртуальное окружение, если нет $ python3 -m venv .venv --prompt VirtualEnv # активируем виртуальное окружение $ source .venv/bin/activate # обновляем `pip` (VirtualEnv):~$ python3 -m pip install -U pip # ставим модуль `blinker` (VirtualEnv):~$ python3 -m pip install blinker
blinker
.Именованные сигналы создаются с помощью функции signal()
:
>>> from blinker import signal >>> initialized = signal('initialized') >>> initialized is signal('initialized') # True
Каждый вызов
signal('name')
возвращает один и тот же объект сигнала, позволяя несвязанным частям кода (разным модулям, плагинам, чему угодно) использовать один и тот же сигнал, не требуя совместного использования кода или специального импорта.
Signal.connect()
: подписка на сигналы blinker
.Метод Signal.connect(receiver, sender=ANY, weak=True)
регистрирует функцию receiver
, которая будет вызываться каждый раз при испускании сигнала. Связанным функциям всегда передается объект, вызвавший генерацию сигнала.
from blinker import signal def subscriber(sender): print(f"Получил сигнал, посланный {sender!r}") >>> ready = signal('ready') >>> ready.connect(subscriber) # <function subscriber at 0x7f03d0122550>
Метод Signal.connect()
также принимает 2 дополнительных аргумента:
sender=blinker.ANY
- аргумент ограничивает уведомления, доставляемые получателю receiver
, только теми сообщениями Signal.send()
, которые отправлены отправителем. Если sender=blinker.ANY
(по умолчанию), то получатель receiver
всегда будет уведомлен. Получатель - sender=blinker.ANY
receiver
может быть подключен к нескольким значениям отправителя в одном и том же Signal
через несколько вызовов Signal.connect()
.weak=True
- если True, то сигнал будет содержать слабую ссылку на получателя и автоматически отключится, когда получатель выйдет за пределы области видимости или отработает сборщик мусора.Signal.send()
: отправка сигналов blinker
.Код, создающий интересующие события, может отправлять уведомления Signal.send(*sender, **kwargs)
всем подключенным получателям.
Принимаемые аргументы:
*sender
- любой объект или None
. Если опущено, то синоним None
. Принимает только один позиционный аргумент.**kwargs
- данные для отправки получателям.Ниже простой класс Processor()
выдает сигнал готовности, когда он собирается что-то обработать, и завершается, когда это делается. Он передает self
методу Processor.send()
, показывая, что этот конкретный экземпляр был ответственен за отправку сигнала.
class Processor: def __init__(self, name): self.name = name def go(self): ready = signal('ready') ready.send(self) print("Обработка.") complete = signal('complete') complete.send(self) def __repr__(self): return f'<Processor {self.name}>' >>> processor_a = Processor('a') >>> processor_a.go() # Получил сигнал, посланный <Processor a> # Обработка.
Обратите внимание на сигнал 'complete'
в методе Processor.go()
. Пока ни один получатель не подключился к complete
, и это нормально. Вызов метода Signal.send()
для сигнала без получателей приведет к тому, что уведомления не будут отправлены, и эти неактивные отправки оптимизированы, чтобы быть как можно более дешевыми.
Подключение к сигналу по умолчанию вызывает функцию приемника, когда его посылает любой отправитель. Функция Signal.connect()
принимает необязательный аргумент sender
, для ограничения подписки одним конкретным отправляющим объектом:
def b_subscriber(sender): print("Пойманный сигнал от processor_b.") assert sender.name == 'b' >>> processor_b = Processor('b') >>> ready.connect(b_subscriber, sender=processor_b) # <function b_subscriber at 0x...>
Эта функция была подписана на готовность, но только при отправке processor_b
:
>>> processor_a.go() # Получил сигнал, посланный <Processor a> # Обработка. >>> processor_b.go() # Получил сигнал, посланный <Processor b> # Пойманный сигнал от processor_b. # Обработка.
blinker
.В метод Signal.send()
могут быть переданы дополнительные ключевые аргументы **kwargs
. Они, в свою очередь, будут переданы подключенным функциям:
send_data = signal('send-data') @send_data.connect def receive_data(sender, **kw): print(f'Пойманный сигнал от {sender!r}, data {kw!r}") return 'received!' >>> result = send_data.send('anonymous', abc=123) # Пойманный сигнал от 'anonymous', data {'abc': 123} >>> result # [(<function receive_data at 0x7f03d0240280>, 'received!')]
Возвращаемое значение метода Signal.send
собирает возвращаемые значения каждой подключенной функции в виде списка пар (receiver_function, return_value)
:
blinker
.Сигналам не нужно давать названия. Конструктор объекта Signal
создает уникальный сигнал при каждом вызове. Например, альтернативная реализация вышеописанного класса Processor
может предоставлять сигналы обработки в виде атрибутов класса:
from blinker import Signal class AltProcessor: on_ready = Signal() on_complete = Signal() def __init__(self, name): self.name = name def go(self): self.on_ready.send(self) print("Альтернативная обработка.") self.on_complete.send(self) def __repr__(self): return f'<AltProcessor {self.name}>'
Signal.connect
в качестве декоратора.В разделах выше можно было видеть возвращаемое значение метода Signal.connect()
. Это позволяет использовать Signal.connect()
в качестве декоратора для функций:
apc = AltProcessor('C') @apc.on_complete.connect def completed(sender): print f"AltProcessor {sender.name} завершен!" >>> apc.go() # Альтернативная обработка. # AltProcessor C завершен!
Несмотря на удобство, эта форма, к сожалению, не позволяет настроить аргументы sender
или weak
которые необходимы для подключенной функции. Для этого можно использовать метод Signal.connect_via()
:
dice_roll = signal('dice_roll') @dice_roll.connect_via(1) @dice_roll.connect_via(3) @dice_roll.connect_via(5) def odd_subscriber(sender): print(f"Наблюдаемый бросок костей {sender!r}.") >>> result = dice_roll.send(3) # Наблюдаемый бросок костей 3.
blinker
.Сигналы оптимизированы для очень быстрой отправки независимо от того, подключены ли приемники или нет. Если ключевые аргументы (данные), которые должны быть отправлены с сигналом, требуют больших затрат для вычисления, то может быть более эффективным сначала проверить, подключены ли какие-либо получатели, проверив свойство Signal.receivers
:
>>> bool(signal('ready').receivers) # True >>> bool(signal('complete').receivers) # False >>> bool(AltProcessor.on_complete.receivers) # True
Также возможна проверка получателя, прослушивающего конкретного отправителя:
>>> signal('ready').has_receivers_for(processor_a) # True