• Await expressions

    From Stefan Ram@3:633/280.2 to All on Sat Jan 27 05:36:50 2024
    In "The Python Language Reference, Release 3.13.0a0",
    there is this section:

    |6.4 Await expression
    |
    |Suspend the execution of coroutine on an awaitable object.
    |Can only be used inside a coroutine function.
    |
    |await_expr ::= "await" primary
    |
    |New in version 3.5.

    . And this is the whole section.

    What I do not understand:

    - Which coroutine is suspended?
    - Which object is the object mentioned?
    - For what purpose is the value of the primary expression used?
    - What does it mean to "suspend something on something"?


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Mild Shock@3:633/280.2 to All on Sat Jan 27 07:08:33 2024

    We say that an object is an awaitable object if it can be used in an
    await expression. Many asyncio APIs are designed to accept awaitables.

    There are three main types of awaitable objects:
    coroutines, Tasks, and Futures.

    Stefan Ram schrieb:
    In "The Python Language Reference, Release 3.13.0a0",
    there is this section:

    |6.4 Await expression
    |
    |Suspend the execution of coroutine on an awaitable object.
    |Can only be used inside a coroutine function.
    |
    |await_expr ::= "await" primary
    |
    |New in version 3.5.

    . And this is the whole section.

    What I do not understand:

    - Which coroutine is suspended?
    - Which object is the object mentioned?
    - For what purpose is the value of the primary expression used?
    - What does it mean to "suspend something on something"?



    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Mild Shock@3:633/280.2 to All on Sat Jan 27 07:14:09 2024
    Maybe consult:

    PEP 492 – Coroutines with async and await syntax
    Created: 09-Apr-2015
    Python-Version: 3.5
    https://peps.python.org/pep-0492/

    Mild Shock schrieb:

    We say that an object is an awaitable object if it can be used in an
    await expression. Many asyncio APIs are designed to accept awaitables.

    There are three main types of awaitable objects:
    coroutines, Tasks, and Futures.

    Stefan Ram schrieb:
    ˙˙ In "The Python Language Reference, Release 3.13.0a0",
    ˙˙ there is this section:

    |6.4 Await expression
    |
    |Suspend the execution of coroutine on an awaitable object.
    |Can only be used inside a coroutine function.
    |
    |await_expr ::= "await" primary
    |
    |New in version 3.5.

    ˙˙ . And this is the whole section.

    ˙˙ What I do not understand:

    ˙˙ - Which coroutine is suspended?
    ˙˙ - Which object is the object mentioned?
    ˙˙ - For what purpose is the value of the primary expression used?
    ˙˙ - What does it mean to "suspend something on something"?




    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Mon Jan 29 02:09:12 2024
    dieter.maurer@online.de writes:
    You can explain a function call without saying much about the called function. >Similarly, you can explain "await <expr>" without saying much about
    "<expr>".

    Thanks, Greg and Dieter! The intention of my post was twofold:
    To better understand "await", but also to suggest that the
    documentation can be improved.

    When I have a syntax section and then a semantics section,
    the syntax introduces terms that can then be referred to
    in the semantics section.

    For example:

    | Syntax:
    |
    |expression + expression
    |
    | Semantic
    |
    |Evaluates to the sum of the values of the two expressions.

    This is clear to me. Notice how "expressions" in the Semantics
    section refers to the two "expression" in the Syntax section.
    What would be less clear:

    | Syntax:
    |
    |expression + expression
    |
    | Semantic
    |
    |Evaluates to the sum of two numbers.

    What "two numbers"? Yes, now we can guess that these numbers must
    be the values of the expressions, but it's less clear.

    I think that the author of the specification could improve
    the specification by addressing my questions from the OP,
    but I am not able to submit a suggestion for a wording myself,
    because I am still learning asyncio.


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Mon Jan 29 23:53:33 2024
    Lawrence D'Oliveiro <ldo@nz.invalid> writes:
    It can be a tricky thing to get to grips with, particularly if you are >trying to understand how it works at a low level.

    Does "await f()" (immediately) call "f()"?

    To find this out, I wrote the following program.

    I assume that the next function executed after executing
    "await sleep()" is "sleep" and encoded this in assertions.
    So far, this assumption never failed.

    So, directly after the eternal task executes "await sleep()",
    it is always the coroutine sleep() that starts, it never
    happens that the other task continues before this happens.

    Also, "caller" in "sleep" is "main", "eternal", or "other" as if
    "sleep" is called from one of those (as one would na‹vely expect).

    So, it seems that "await f()" indeed will call f().

    import asyncio as _asyncio
    import inspect as _inspect
    import random as _random

    next = ''

    async def sleep():
    global next
    assert next == 'sleep'
    next = ''
    caller = ''
    frameinfo = _inspect.stack( 1 )
    if frameinfo: caller = frameinfo[ 1 ].function
    await _asyncio.sleep( 0.0000000001 )

    async def eternal():
    global next
    while True:
    assert next != 'sleep'
    next = 'sleep'
    await sleep()

    async def other():
    global next
    while True:
    assert next != 'sleep'
    next = 'sleep'
    await sleep()

    async def main():
    global next
    assert next != 'sleep'
    eternal_task = _asyncio.get_running_loop().create_task( eternal() )
    next = 'sleep'
    await sleep()
    other_task = _asyncio.get_running_loop().create_task( other() )
    await eternal_task

    _asyncio.run( main() )


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Thu Feb 1 01:43:15 2024
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    |await_expr ::= "await" primary
    |Suspend the execution of coroutine on an awaitable object.

    My ideas of language specifications are that they should
    contain all the information to write an implementation of the
    language. I am not sure whether the Python Language Reference
    already fullfils this criterion. BUT I have made one step
    in the direction of better understanding "await":

    main.py

    import dis

    def f(): return g()

    dis.dis( f )
    print()

    async def f(): return await g()

    dis.dis( f )

    sys.stdout

    3 0 LOAD_GLOBAL 0 (g)
    2 CALL_FUNCTION 0
    4 RETURN_VALUE

    8 0 LOAD_GLOBAL 0 (g)
    2 CALL_FUNCTION 0
    4 GET_AWAITABLE
    6 LOAD_CONST 0 (None)
    8 YIELD_FROM
    10 RETURN_VALUE

    . So, now it's down to finding documentation for the "GET_AWAITABLE"
    and "YIELD_FROM" opcodes! One can guess that GET_AWAITABLE just gets
    a kind of reference to the coroutine g. So the core of await must be
    YIELD_FROM. Web:

    |In Python 3.11 and above, the YIELD_FROM opcode is replaced
    |by a SEND + YIELD_VALUE while loop, as documented in the
    |SEND(target_delta).

    . Oh, this seems to be a moving target!

    |# yield from subgenerator is implemented as the following loop
    |# (with None initially at the top of the stack)
    |#
    |# SEND (sends the top of stack to the subgenerator)
    |# YIELD_VALUE (returns the yielded value to the caller)
    |# JUMP_BACKWARDS (to SEND)

    Ok, but this just seems to spell out what "YIELD_FROM" does,
    so it should still be true that "await" boils down to "YIELD_FROM".
    (Yes, Greg already kindly answered something to that effect.)


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Thu Feb 1 21:09:10 2024
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    In "The Python Language Reference, Release 3.13.0a0",
    there is this section:
    |6.4 Await expression
    |Suspend the execution of coroutine on an awaitable object.
    |Can only be used inside a coroutine function.
    |await_expr ::= "await" primary

    A wording I like is what I found in the World-Wide Web where
    Victor Skvortsov wrote:

    |When we await on some object, await first checks whether the
    |object is a native coroutine or a generator-based coroutine,
    |in which case it "yields from" the coroutine. Otherwise, it
    |"yields from" the iterator returned by the object's
    |__await__() method.
    Victor Skvortsov (2021).

    This actually explains "to wait on some object" (which might be the
    same as to "suspend on some object"), and I was not able to find
    such an explanation in the venerable Python Language Reference!

    Heck, even of the respected members of this newsgroup, IIRC, no one
    mentioned "__await__". So, let's give a big shoutout to Victor!


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Fri Feb 2 03:39:44 2024
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    Heck, even of the respected members of this newsgroup, IIRC, no one
    mentioned "__await__". So, let's give a big shoutout to Victor!

    It should be possible to understand "await" in isolation (in
    the spirit of the quotation I posted to "comp.lang.lisp" today.)
    For example, use "await" without using "asycnio". So here's
    an example program in that direction I just wrote.

    main.py

    class o_class():
    def __await__( self ):
    print( "__await__ called." )
    return iter( [ 'a', 'b', 'c' ])

    o = o_class()

    async def f():
    while 1: await o

    for i, j in enumerate( f().__await__() ):
    print( j )
    if 5 == i: break

    sys.stdout

    __await__ called.
    a
    b
    c
    __await__ called.
    a
    b
    c

    (Then, it should also be interesting to understand how asyncio
    uses "await" to implement asynchronous I/O.)


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Fri Feb 2 04:32:58 2024
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    (Then, it should also be interesting to understand how asyncio
    uses "await" to implement asynchronous I/O.)

    I have taken "sleep" from asyncio\tasks.py and simplified it a bit:

    async def sleep( delay, result=None, *, loop=None ):

    loop = events.get_running_loop()

    future = loop.create_future()

    h = loop.call_later
    ( delay, futures._set_result_unless_cancelled, future, result )

    try: return await future
    finally: h.cancel()

    So, this is how the control is transferred to the event
    loop after an "await sleep"! Initially, the control goes
    to "sleep", but this transfers the control to the event loop
    (until "sleep" stops waiting for its future in "await future").


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)
  • From Stefan Ram@3:633/280.2 to All on Fri Feb 2 20:12:06 2024
    ram@zedat.fu-berlin.de (Stefan Ram) writes:
    So, this is how the control is transferred to the event
    loop after an "await sleep"! Initially, the control goes
    to "sleep", but this transfers the control to the event loop
    (until "sleep" stops waiting for its future in "await future").

    And, to answer one final question, let me quote the Web again.
    The author name is not indicated clearly, but probably it's "Bharel":

    |The final burning question we must answer is - how is the IO
    |implemented?
    ....
    |The IO part of the event loop is built upon a single crucial
    |function called "select".
    ....
    |When all available tasks are waiting for futures, the event
    |loop calls select and waits. When the one of the sockets has
    |incoming data, or its send buffer drained up, asyncio checks
    |for the future object tied to that socket, and sets it to
    |done.
    ....
    probably Bharel.


    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: Stefan Ram (3:633/280.2@fidonet)