Установка дескриптора в python3.5 асинхронно

Я могу написать дескриптор, возвращающий будущее, которого можно ожидать.

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.

Как бы я установил этот атрибут?

  • await device.attr=5 -> SyntaxError: невозможно назначить выражение для ожидания
  • await setattr(device, 'attr', 5) -> TypeError: объект NoneType не может использоваться в выражении «ожидание»
  • device.attr=5 -> RuntimeWarning: сопрограмма '__set__' никогда не ожидалась

person Günther Jena    schedule 11.09.2016    source источник
comment
__set__ не должен ничего возвращать. Вы хотели, чтобы value разрешалось асинхронно?   -  person jonrsharpe    schedule 11.09.2016
comment
Да, это будет целью   -  person Günther Jena    schedule 11.09.2016
comment
Но все ваши примеры передают значение, а не будущее.   -  person jonrsharpe    schedule 11.09.2016


Ответы (3)


То, что вы пытаетесь сделать, невозможно (с Python 3.5).

Хотя для __get__ может быть разумно возвращать Future, создание __set__ асинхронного просто не поддерживается Python 3.5. Возвращаемое значение __set__ игнорируется Python, поскольку нет «возвращаемого значения» присваиваний. И вызов __set__ всегда синхронен. Что-то вроде a = (b.c = 5) на самом деле вызывает SyntaxError, как вы уже заметили.

Если бы асинхронные назначения, такие как await device.attr = 5, были разрешены, вероятно, был бы отдельный протокол для асинхронных дескрипторов, то есть с сопрограммами __aget__ и __aset__ в качестве специальных методов по аналогии с асинхронным диспетчером контекста (async with/__aenter__) и асинхронной итерацией (async for/__aiter__). См. PEP 492, чтобы узнать о дизайнерских решениях, лежащих в основе поддержки async / await.

Также обратите внимание, что __get__ возвращение будущего не делает __get__ сопрограммой.

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

person code_onkel    schedule 11.09.2016
comment
Привет, хотя ваш ответ является идеальным, который я хотел бы услышать в курсе Python, и заслуживает своей оценки, я бы не сказал, что в python что-то невозможно. Обнаружение собственного способа делать/выражать вещи делает его очень живым языком, который можно настроить практически для любой ситуации. - person Pmp P.; 02.12.2018

Конструкция await setattr(device,'attr',5) также возможна и на самом деле более прилична, чем device.attr = 5, но, конечно, не перегружайте подлинный setattr.

этот на самом деле полезен для асинхронного безпоточности, сохраняя при этом удобочитаемость. работающий код, который устанавливает значение без ожидания, вызовет приятное «Предупреждение о выполнении: сопрограмма attr.__set__ никогда не ожидалась»

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())
person Pmp P.    schedule 02.12.2018
comment
Разве это не избавляет от большого преимущества декораторов как способа изменить поведение нормально выглядящего доступа к атрибутам? - person Patrick Haugh; 03.12.2018
comment
может быть, но я попытался показать 1 возможный способ добавления aset, предложенный code_onkel, и который, как мне кажется, более явный, чем декораторы. - person Pmp P.; 06.12.2018

но python великолепен, поэтому, если вы хотите написать device.attr = 5, но ожидаете операции установки, вы просто можете это сделать (я не говорю, что это хорошая идея, хотя это может быть полезно на безпоточном питоне сценарий) с помощью контекстного блока:

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())
person Pmp P.    schedule 02.12.2018