异步设置 python3.5 中的描述符

Setting a descriptor in python3.5 asynchronously

我可以写一个描述符返回一个可以等待的未来。

class AsyncDescriptor:
    def __get__(self, obj, cls=None):
         # generate some async future here
         return future

    def __set__(self, obj, value):
         # generate some async future here
         return future

class Device:
    attr=AsyncDescriptor()

device=Device()

现在我可以使用 value=await device.attr 在协程中获取值。

我该如何设置这个属性?

你试图做的事情是不可能的(Python 3.5)。

虽然 __get__ 到 return 一个 Future 可能是明智的,Python 3.5 根本不支持 __set__ 异步。 __set__ 的 return 值被 Python 忽略,因为没有 "return value" 赋值。并且对 __set__ 的调用始终是同步的。正如您已经注意到的,像 a = (b.c = 5) 这样的东西实际上会引发 SyntaxError

如果允许像 await device.attr = 5 这样的异步赋值,那么可能会有一个单独的异步描述符协议,即协程 __aget____aset__ 作为类比异步上下文的特殊方法管理器 (async with / __aenter__ ) 和异步迭代 (async for / __aiter__)。有关 async / await 支持背后的设计决策,请参阅 PEP 492

另请注意,__get__ return未来不会使 __get__ 成为协程。

在没有进一步上下文的情况下,您似乎想在描述符协议提供的属性访问抽象背后隐藏一些东西,最好明确地完成,但这当然取决于您。

但是 python 很棒,所以如果你 写 device.attr = 5 但等待集合 操作,你可以在上下文块的帮​​助下(我不是说这是个好主意,尽管它可能对无线程 python 场景有用):

import asyncio, sys

async def main():

    obj.attr = "not async value"
    print(obj.attr)

    print()
    print("now set with an async value")

    async with aio(obj):
        # ============== yes i can, thanks python ! ===
        obj.attr = 5
        # =============================================

    print(obj.attr)

    print("bye")
    flush_io()

def flush_io():
    sys.stdout.flush()
    sys.stderr.flush()

class attr:
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, objtype):
        if obj is None:
            print("__get__", obj, objtype)
            return self
        print("get", obj, objtype)
        return self.value

    def __set__(self, obj, value):
        if value is not obj.__class__:
            if obj in aio.updating:
                aio.setlist.append([self, value])
                print("    future set", self, value)
                return

            self.value = value
            print("set", obj, value)
            return
        print("__set__", obj, value)

    async def setter(self, value):
        print("    async updating", self, end=" ")
        for i in range(value):
            await asyncio.sleep(1)
            print(".", end="")
            flush_io()
        print()
        self.value = value

    def __repr__(self):
        return "<async attr>"

class aobj:

    attr = attr("empty")

    def __repr__(self):
        return "<aobj>"

class aio:
    updating = []
    setlist = []

    def __call__(self, obj):
        self.updating.append(obj)
        return self

    async def __aenter__(self):
        print("aenter", self.updating)

    async def __aexit__(self, *tb):
        self.updating.pop()
        while len(self.setlist):
            obj, value = self.setlist.pop()
            await obj.setter(value)
        print("aexit")


aio = aio()
obj = aobj()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

await setattr(device,'attr',5) 构造也是可能的,实际上比 device.attr = 5 更体面,但当然不要超载真正的 setattr。

这个实际上对异步无线程很有用,同时保持易读性。 运行 不等待设置值的代码将引发一个很好的 "RuntimeWarning: coroutine attr.__set__ was never awaited"

import asyncio, sys

async def main():

    obj.attr = "not async value"
    print(obj.attr)

    print()
    print("now give set an async value")

    #DO NOT DO THAT use aio.asetattr(obj,'attr',5)
    setattr = aio.asetattr
    # ============== yes i can, thanks python ! ===========
    await setattr(obj,'attr',5)
    # ======================================

    print(obj.attr)

    print("bye")
    flush_io()

def flush_io():
    sys.stdout.flush()
    sys.stderr.flush()

class attr:
    def __init__(self, value):
        self.value = value

    def __get__(self, obj, objtype):
        if obj is None:
            return self
        return self.value

    async def __set__(self, obj, value):
        if value is not obj.__class__:
            print("    async updating", self, end=" ")
            for i in range(value):
                await asyncio.sleep(1)
                print(".", end="")
                flush_io()
            print()
            self.value = value
            print("set", obj, value)
            return
        print("__set__", obj, value)


    def __repr__(self):
        return "<async attr>"

class aobj:

    attr = attr("empty")

    def __repr__(self):
        return "<aobj>"

class aio:

    async def asetattr(self, obj, attr, value):
        await asyncio.sleep(1)
        a_attr = getattr( type(obj), attr)
        await a_attr.__set__(obj, value)
        print("done!")

aio = aio()
obj = aobj()

loop = asyncio.get_event_loop()
loop.run_until_complete(main())