首页 > 解决方案 > Pykka 对 @property 设置器的行为

问题描述

我在玩 pykka 的演员模型,发现了一些有趣的行为。这是一个演示

  1. 启动一个演员
  2. 获取它的代理
  3. 设置其@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]

我在这里有几个问题。

  1. 为什么 gettersupportThread.myVal()在被调用时会在主线程的上下文中.proxy()被调用?
  2. 为什么这些行会supportProxy.myVal = <a new value>导致主线程等待actor完成?

这对我来说似乎是一个错误:我认为只有.get()在 ThreadingFuture 上调用代理时才应该阻止执行。或者这是故意的?

标签: pythonpykka

解决方案


免责声明:我是 Pykka 的作者。

顺便说一句:Pykka 并没有死,它只是为它的用途而工作得很好:为Mopidy 音乐服务器及其 100 多个扩展提供并发抽象。

Pykka 的属性行为并不是最优的,但它是有原因的。

  1. 要创建代理对象,Pykka 必须自省目标对象的 API。当测试目标对象上可用的属性是否是可调用的、属性或“可遍历的属性”时,getattr()在每个属性上调用一次。这会导致调用属性 getter。见Proxy._get_attributes()Actor._get_attribute_from_path()为。

  2. 由于 Python 中无法从属性设置器捕获返回值,因此 Pykka 代理上的属性设置采用等待设置器完成的安全默认设置,以便在设置器中引发的任何异常都可以在mainThread(). 另一种方法是不处理由属性设置器引发的任何异常。详情请参阅Proxy.__setattr__()

总而言之,属性与 Pykka 一起“工作”,但您可以通过使用方法调用获得更多控制权,因为您总是可以返回未来,并且可以自行决定是否需要等待结果。

无论您是否使用 Pykka,我都认为最好不要在属性 getter 中做任何昂贵的工作,而是使用适当的方法来做“昂贵”的工作。

API 设计直接影响用户使用 API 的方式:

  • 具有属性的对象会导致重复使用相同的属性,从而重复重新计算。保持简单和便宜的属性。
  • 公开返回结果的方法的对象通常会导致调用者将结果保存在变量中并重用相同的结果,而不是多次调用该方法。对任何不平凡的工作使用方法。如果它真的很昂贵,请考虑使用另一个前缀get_,例如load_, fetch_or calculate_,进一步表明用户应该保留对结果的句柄并重用它。

为了自己遵循这些原则,Mopidy 的核心 API 很久以前就从使用大量属性迁移到使用 getter 和 setter。在下一个主要版本中,所有属性将从 Mopidy 的核心 API 中删除。由于代理创建的工作方式,此清理将大大减少 Mopidy 的启动时间。


推荐阅读