Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Function colouring is a big problem in Python

Not when you know how to call sync functions from async functions and vice versa.

An sync function can call an async function via:

  loop = asyncio.new_event_loop()
  result = loop.run_until_complete(asyncio.ensure_future(red(x)))
A async function can call a sync function via:

  loop = asyncio.get_event_loop()
  result = await loop.run_in_executor(None, blue, x)
Where red and blue are defined as:

  async def red(x):
        pass

  def blue(x):
      pass
Note that the documentation is wrong about recommending create_task over ensure_future. That recommendation results in more restrictive code as create_task only accepts a coroutine and not a task.

This works for regular functions I don't know how it works for generators.



You perfectly illustrated why this is a problem. Calling functions from one side to the other involves ceremony. Ceremony adds cognitive overhead and decreases readability.


Would that work for you ?

    result = asyncio.run(red(x))
That's calling async function red from non-async code.

Not seeing any particular readability issue with that usage neither. If you don't call asyncio.run or await on the result of an async function call, then you get a coroutine for result.


> result = asyncio.run(red(x))

Still much harder to think about/read then

   result = red(x)
We should be finding ways to get to the latter with concurrency. async/await is at best a patchwork compromise until we can do better.


I much prefer the performance implications of my code be explicit than implicit.

Async in this case has less ceremony than threads. But still enough to make thing explicit.


I've written Python functions that "call" another function either async or not depending on how the function inspects.

For instance, imagine a "maybe_await" method that just calls sync if is synchronous or otherwise awaits.


That's trivial:

    result = yourfunc()
    if inspect.iscoroutine(result):
       result = asyncio.run(result)
If I have code like that, it's at one place per library, didn't need to encapsulate it in a maybe_await function.


It's very heavy to do this is it not? Like you inspect the function on each call to figure out if it's async or not?


For things that happen at UI speed it isn't bad. If I wanted to do something a million times a second I'd worry about it.

Some Javascript frameworks, such as Vue, often do something similar in that you can pass either a sync or async callback and it does the right thing for either. In that case you could potentially inspect the function once and call it many times.


You don't inspect the function, but the result. Is it a coroutine ? If so, it needs to be awaited (see my example above).

I believe this also would allow non-async function to return a coroutine I suppose.

Anyway, in this case chances are that there will be no performance overhead if there's any io bound operation running in the coroutine so it should run the iscoroutine check of the result and the await call before the function is done.


Writing such code to call between sync/async makes me cry man - this is so ugly. I'd still consider it a problem.


Actually Python now offers an asyncio.run function. Your example may now be:

result = asyncio.run(red(x))


alternatively, one can use gevent and get a transparent asyncio from a modified runtime - something that a high-level language should've provided out of the box.


Hiding awaitables from the language, sounds like against the zen (explicit better than implicit)

For example, when someone access a descriptor in Django.. this could end being a query to the db (transparent) but dangerous. With asyncio you explicitly await something to return the execution to the event loop.

At least for me sounds like a safer behaviour


Hiding awaits would be against the zen. As you explained, it's a behaviour difference that might matter. The await forces you to think about it, and that's safer.

But the difference between asyncio.run(red(x)) and blue(x)... isn't. There's no difference which matters. They are just different implementations of the same behaviour.

If red and blue are both the same DB query, with the only difference being red is async-style and blue sync-style, these two lines have exactly the same program behaviour:

    result = asyncio.run(red(x))
and

    result = blue(x)
So the asyncio.run is just cognitive fog. It forces you to think about the type difference, but doesn't add any safety.

It's almost the opposite of Python's usual duck-typing parsimony, which normally allows equivalent things to be used in place of each other without ceremony.


> Hiding awaitables from the language, sounds like against the zen (explicit better than implicit)

Zen is not respected by explicit asyncio, just try to compose asyncio with iterators [1]

[1] https://stackoverflow.com/questions/42448664/async-generator...

This problem doesn't exist with gevent, and composability is a desired thing in any programming language. Python's asyncio fractioned the community that was previously doing implicit asyncio with sync interfaces, and the current state of API is not an example of composable primitives that follow the Zen of Python:

> Beautiful is better than ugly.

> Simple is better than complex.

> Readability counts.

> Special cases aren't special enough to break the rules.


Read that thread in full, it's also a case of explicit is better than implicit. (Async for vs for loops, essentially)


Should I start changing my code, from "foo.bar" to "foo.__getattribute__('bar')" ? Probably a bad compare, but I'm looking for someone to tell me why.

Meanwhile, my pretty python foo = bar().something has gone all foo = (await bar()).something


What does gevent do - give Python something similar to Goroutines?


yes, pretty much, with a few specifics - https://sdiehl.github.io/gevent-tutorial/#greenlets




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: