Поведение Пикки с установщиками @property
Я играю с актерской моделью Пикки и обнаружил забавное поведение. Вот демо, которое
- Запускает актера
- Получает прокси к нему
- Устанавливает один из его @properties
Вот код:
import time
import pykka
from sys import version_info as python_version
if python_version > (3, 0):
from _thread import get_ident
else:
from thread import get_ident
startTime = time.time()
def debug(msg, prefix='MSG'):
msgProc = "%s (thread #%s @ t = %.2fs): %s" % (prefix,get_ident(), time.time() - startTime, msg)
print(msgProc)
def mainThread():
debug('Launching support actor...', prefix='MAIN')
supportRef = supportThread.start()
debug('Getting support proxy...', prefix='MAIN')
supportProxy = supportRef.proxy()
debug('Getting myVal obj...', prefix='MAIN')
obj = supportProxy.myVal
debug(obj, prefix='MAIN')
debug('Setting myVal obj...', prefix='MAIN')
supportProxy.myVal = 2
debug('Setting myVal obj...', prefix='MAIN')
supportProxy.myVal = 3
supportProxy.stop()
class supportThread(pykka.ThreadingActor):
def __init__(self):
super(supportThread, self).__init__()
self._myVal = 0
@property
def myVal(self):
debug("Getting value", prefix='SUPPORT')
return self._myVal
@myVal.setter
def myVal(self, value):
debug("Setting value: processing for 1s...", prefix='SUPPORT')
time.sleep(1)
debug("Setting value: done", prefix='SUPPORT')
self._myVal = value
mainThread()
Вывод выглядит так:
MAIN (thread #16344 @ t = 0.00s): Launching support actor...
MAIN (thread #16344 @ t = 0.00s): Getting support proxy...
SUPPORT (thread #16344 @ t = 0.00s): Getting value
MAIN (thread #16344 @ t = 0.00s): Getting myVal obj...
MAIN (thread #16344 @ t = 0.00s): <pykka.threading.ThreadingFuture object at 0x0000000002998518>
MAIN (thread #16344 @ t = 0.00s): Setting myVal obj...
SUPPORT (thread #16248 @ t = 0.00s): Getting value
SUPPORT (thread #16248 @ t = 0.00s): Setting value: processing for 1s...
SUPPORT (thread #16248 @ t = 1.00s): Setting value: done
MAIN (thread #16344 @ t = 1.00s): Setting myVal obj...
SUPPORT (thread #16248 @ t = 1.01s): Setting value: processing for 1s...
SUPPORT (thread #16248 @ t = 2.01s): Setting value: done
[Finished in 2.3s]
У меня есть пара вопросов здесь.
- Почему геттер
supportThread.myVal()
вызывается в контексте основного потока, когда.proxy()
называется? - Почему линии
supportProxy.myVal = <a new value>
в результате чего основной поток ожидает завершения актера?
Это кажется мне ошибкой: я думал, что прокси должен блокировать выполнение, только если .get()
вызывается в ThreadingFuture. Или это предназначено?
1 ответ
Отказ от ответственности: я автор Pykka.
Кроме того: Pykka не умерла, она просто отлично работает для того, для чего она была создана: предоставляя абстракцию параллелизма для музыкального сервера Mopidy и его более 100 расширений.
Поведение Пикки со свойствами не является оптимальным, но есть причина, по которой это так.
Чтобы создать прокси-объект, Pykka должен проанализировать API целевого объекта. При проверке, являются ли атрибуты, доступные на целевом объекте, вызываемыми, атрибутами или "проходимыми атрибутами",
getattr()
вызывается один раз для каждого атрибута. Это заставляет свойство get вызываться. УвидетьProxy._get_attributes()
а такжеActor._get_attribute_from_path()
за.Так как в Python нет способа получить возвращаемое значение из установщика свойств, настройка свойства на прокси-сервере Pykka принимает безопасное значение по умолчанию ожидания завершения установки, так что любые исключения, возникающие в установщике, могут быть повторно вызваны на сайте вызова в
mainThread()
, Альтернативой было бы оставить любые исключения, вызванные установщиками свойств, необработанными. УвидетьProxy.__setattr__()
для деталей.
Таким образом, свойства "работают" с Pykka, но вы получаете больше контроля, используя вызовы методов, так как вы всегда получаете будущее и можете сами решить, ждать вам результата или нет.
Независимо от того, используете ли вы Pykka или нет, я считаю хорошей практикой не делать дорогостоящей работы с геттерами, а вместо этого использовать надлежащие методы для выполнения "дорогой" работы.
Разработка API напрямую влияет на то, как ваши пользователи будут использовать API:
- Объект со свойством приглашает к повторному использованию одного и того же свойства и, следовательно, к повторному пересчету. Сохраняйте свойства мертвыми простыми и дешевыми.
- Объект, представляющий метод, возвращающий результат, обычно приводит к тому, что вызывающая сторона сохраняет результат в переменной и повторно использует один и тот же результат вместо вызова метода несколько раз. Используйте методы для любой нетривиальной работы. Если это действительно дорого, рассмотрите другой префикс, чем
get_
, напримерload_
,fetch_
, или жеcalculate_
дальнейшее указание на то, что пользователь должен держать указатель на результат и использовать его повторно.
Чтобы самому следовать этим принципам, основной API Mopidy давно перешел от использования множества свойств к использованию методов получения и установки. В следующем основном выпуске все свойства будут удалены из основного API Mopidy. Благодаря тому, как работает создание прокси, эта очистка значительно сократит время запуска Mopidy.