Interactive interpreter with standard input() and time.sleep()

159 views
Skip to first unread message

Pierre Quentel

unread,
May 23, 2022, 6:12:55 AM5/23/22
to brython
One of the most requested features in Brython has always been, it is possible to implement input() inside the interactive interpreter as with the CPython REPL, instead of the ugly popup input box ?

The answer so far was, it's not possible because there is no way to define blocking functions in Javascript.

When I tested the REPL provided by the team that has implemented CPython in WASM, I noticed that they had found the solution ! After looking at the code, I found that this is possible thanks to a Javascript primitive that I had never heard of before, Atomics.

I have implemented it and I am pleased to announce the new version on the Brython site. You will now be able to run code such as below:

repl_with_input.png

You can also use time.sleep() as in the standard CPython interpreter.

While this is a progress compared to the current version, there are a few limitations:

- the technology that makes it possible to block execution until the user has entered input, or until the specified number of seconds has passed, is only available in Web Workers - and in Web Workers, there is no access to the DOM. This means that "from browser import document" will fail, for instance. For teaching purposes this should not be a big issue.

- this technology uses a Javascript object, SharedArrayBuffer, which is currently only available if the server sends specific HTTP headers (cross-origin-embedder-policy= 'require-corp' and cross-origin-opener-policy= 'same-origin'). I have modified the built-in Brython server (server.py) to send these headers, and have added them in the configuration of the HTTP server at brython.info, but if you want to serve the same REPL on another machine you will have to make sure that the server is configured to send these headers.

This is still experimental, and the version at brython.info uses the current development version which is ahead of the latest release. You can test it locally with the built-in server at /tests/console_input.html and report issues in the tracker as usual.

Edward Elliott

unread,
May 23, 2022, 7:16:08 AM5/23/22
to bry...@googlegroups.com
Thanks Pierre.  Neat solution.

I don't quite understand what the new console is meant to replace.  Will this replace the console page on the brython site at https://brython.info/tests/console.html?  I hope not, as I use "from browser import document" there frequently.

Brython console (on brython.info) is very useful for testing and inspecting DOM elements and their properties / methods interactively.  Even though my document will be different, I can test how brython interacts with JS objects returned by DOM calls, or how different handlers behave.  It would be a shame to lose that behavior.

If console_input.html is meant to be a separate page that doesn't replace console.html, then that's no problem.

Thanks for clarifying.
Edward


--
You received this message because you are subscribed to the Google Groups "brython" group.
To unsubscribe from this group and stop receiving emails from it, send an email to brython+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/brython/b428457b-4ce0-4b75-80e4-78e5c2808096n%40googlegroups.com.

Pierre Quentel

unread,
May 26, 2022, 3:39:06 PM5/26/22
to brython
Le lundi 23 mai 2022 à 13:16:08 UTC+2, Edward Elliott a écrit :
Thanks Pierre.  Neat solution.

I don't quite understand what the new console is meant to replace.  Will this replace the console page on the brython site at https://brython.info/tests/console.html?  I hope not, as I use "from browser import document" there frequently.

Brython console (on brython.info) is very useful for testing and inspecting DOM elements and their properties / methods interactively.  Even though my document will be different, I can test how brython interacts with JS objects returned by DOM calls, or how different handlers behave.  It would be a shame to lose that behavior.

If console_input.html is meant to be a separate page that doesn't replace console.html, then that's no problem.

Thanks for clarifying.
Edward

If I can find a way to access the DOM from the new version, it could replace the old one, but for the moment I keep both.
- Pierre

Olliver Aira

unread,
Apr 29, 2024, 7:56:01 PMApr 29
to brython
Out of curioisity, couldnt you make use of JavaScript's `async` `await` syntax & something like:
```js
function sleep(seconds) { return new Promise(resolve => setTimeout(resolve,seconds/1000)); }
```
To enable sleep in all contexts?

Pierre Quentel

unread,
Apr 30, 2024, 8:29:25 AMApr 30
to brython
Le mardi 30 avril 2024 à 01:56:01 UTC+2, ollive...@gmail.com a écrit :
Out of curioisity, couldnt you make use of JavaScript's `async` `await` syntax & something like:
```js
function sleep(seconds) { return new Promise(resolve => setTimeout(resolve,seconds/1000)); }
```
To enable sleep in all contexts?

Unfortunately not, because the function returns immediately, not after the specified number of seconds.

If you have Python code such as

time.sleep(2)
run_func_after_2_seconds()
 
the function will be called immediately after the call to time.sleep(2), it will not wait 2 seconds.

Olliver Aira

unread,
Apr 30, 2024, 10:54:16 AMApr 30
to brython
I see, but couldn't the emulated Python code be executed in an async context to support Promises (that is, if the interpreter were to run inside a JavaScript "module")?

Pierre Quentel

unread,
May 1, 2024, 3:35:53 AMMay 1
to brython
Brython actually supports promises; with the code below the function is called after 2 seconds

<script type="text/python" debug="10">
from browser import window, aio

async def main():
  await window.sleep(2)
  call_function_after_2_seconds()

aio.run(main())
</script>

<script>
function sleep(seconds) {
  return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}
</script>

The problem is that when Brython translates a Python source code to Javascript, there is no way to guess if a function call should be blocking or not, so if it should be wrapped in an async function and "awaited" or not.

Olliver Aira

unread,
May 2, 2024, 11:29:27 AMMay 2
to brython
Makes sense, I was gonna suggest simply running all emulated code in async contexts, but it might not be worth the performance cost? But still, maybe it could be enabled as an opt in if this would be possible at all? Eg: <script type="text/python" enable-blocking-operations="True"> 

So, if a Python function is defined without the async prefix - run the JavaScript equivalent in async anyway and implicitly await any promises returned by any called functions inside it (allowing it to run async code while maintaining non-async syntax).


I also did a quick little test to get an idea about how severe any Performance penalty would be:
<script type="module">
function add(a,b) {return a+b;}
let i = 0;
let startTimeMs=Date.now();
while(i < 1000000) {
  i=add(i,1);
}
let endTimeMs=Date.now();
console.log(`loop without await finished after ${endTimeMs-startTimeMs} ms`);


async function addAsync(a,b) {return a+b;}
i = 0;
startTimeMs=Date.now();
while(i < 1000000) {
  i=await addAsync(i,1);
}
endTimeMs=Date.now();
console.log(`awaited loop finished after ${endTimeMs-startTimeMs} ms`);
</script>
<script type="text/python">
import time
def add(a,b):
    return a+b
i = 0
startTimeMs=time.time()
while i < 1000000:
    i=add(i,1)
endTimeMs=time.time()
print(f"Brython equivalent {(endTimeMs-startTimeMs)*1000} ms")


from browser import aio
async def addAsync(a,b):
    return a+b
async def main():
    i = 0
    startTimeMs=time.time()
    while i < 1000000:
        i=await addAsync(i,1)
    endTimeMs=time.time()
    print(f"awaited Brython equivalent {(endTimeMs-startTimeMs)*1000} ms");
aio.run(main())
</script>
loop without await finished after 1 ms
awaited loop without await finished after 1381 ms
Brython equivalent 137.00008392333984 ms
awaited Brython equivalent 7870.000123977661 ms


might look quite bad from a glance, but for some lighter code (such as UI code or for educational purposes) I'm not so sure this has to be a deal breaker? To reduce the performance penalty, Python built in functions and anything that ships as a part of  brython_stdlib.js could still be executed synchronously with them being able to opt-in for the same behavior on a per-function basis (eg via a decorator @aio.enable_blocking_operations() which would be used by functions that require async, such as time.sleep()). Likewise, for performance sensitive functions declared inside of modules initialized with enable-blocking-operations="True" there could also be decorator for excluding certain functions, like  @aio.disable_blocking_operations().

Under the hood, this could be implemented by adding a magic attribute __enable_blocking_operations__:bool that defaults to  False  for all functions unless declared inside a enable-blocking-operations="True" module. The @aio.disable_blocking_operations() decorator simply sets the attribute to  False while @aio.enable_blocking_operations() sets it to True. When a Python function is called, its JS equivalent will be awaited if __enable_blocking_operations__  is  True & raise a runtime Python Exception if its is False but tries to call a function of which __enable_blocking_operations__ attribute is True (which could be thrown while the function is executing - hence Brython wouldn't need to do any ahead-of-time guesswork).

(I hope that makes sense 😅)

Denis Migdal

unread,
May 2, 2024, 11:52:36 AMMay 2
to bry...@googlegroups.com
Doesn't it seems quite complex ?
The more the code is complex, the more likely we would get bugs on unexpected use cases, and the harder to maintain.

You can have 2 solutions :
- starting an AJAX query to a server that will never answer, with a timeout.
- using mutex in a webworker.

--
You received this message because you are subscribed to the Google Groups "brython" group.
To unsubscribe from this group and stop receiving emails from it, send an email to brython+u...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages