3 ms·
It should have been implemented as functions or attributes instead of language syntax sugar. Like Lua did. Like Rust did. Imagine this: def foo():
by VWWHFSfQ 6y ago
It should have been implemented as functions or attributes instead of language syntax sugar.
Like Lua did. Like Rust did.
Imagine this:
def foo():
return "bar"
async def foo():
return "bar"
How do you know how to call that function? It's called the same thing. It has the same signature.. It returns the same thing. One is sync, the other is async. This is "colored functions".
You have to call the 2nd one like:
await foo()
why do you have to care about that? It's either a coroutine or it's not. All functions in Python should just be callable like normal functions. But the Computer Scientists went and messed that up and made colored functions.
It's absurd.
- tick_tock_tick 6y agoIsn't your example exactly how Rust works to?
- haimez 6y agoRust is statically typed, so it’s not quite the same effect
- contravariant 6y agoThis was already the case with generators wasn't it? Worse in that case it depend on the yield statement, which cannot be seen from the function definition. In fact I'm pretty sure generators and async/await are almost the same feature, with possibly just some additional logic to allow await to suspend computation (so you don't have to write a loop `while not done(): sleep()` manually).
- pansa2 6y agoYes, `await` is just a modified form of `yield from`.
- pansa2 6y agoThe argument for requiring `await` is that it makes explicit which calls might yield control back to the event loop. With Lua’s stackful coroutines, this information would either be hidden or just a naming convention, e.g. read_file vs read_file_async.
- pdonis 6y agoI don't mind the "await foo()" so much; as others have noted, it makes explicit that your code is yielding control at that point. What I mind is having to do "async def", "async for", "async with" all over the place for no good reason. Python didn't do that with generators; you defined an ordinary function, and if it had a "yield" somewhere in its body, it returned a generator. I don't have to do "gen def", "gen with", "gen for", etc. to remind the interpreter that it's a generator. The obvious way to handle async coroutines is the same: you define an ordinary function, and if it has "await" somewhere in its body, it's a coroutine. I shouldn't have to be reminding the interpreter everywhere that it's a coroutine. (The reason "await" is a special case is that, with a generator, it's just an ordinary iterable, so it's not yielding control if I just use it like any other iterable, say in a for statement. If I want to do something with the generator that might yield control (as in the "using generators as coroutines" paradigm that came before async/await), I have to call its "send()" method, or otherwise do something special that will stand out. So there's no need to add a keyword for it. But with a coroutine, ordinary call sites that wouldn't yield control with any other kind of function can yield control, so it makes sense to add a keyword to make that explicit.)
- pansa2 6y ago> What I mind is having to do "async def", "async for", "async with" all over the place for no good reason. IMO `async for` and `async with` are misnomers - they are statements that call asynchronous functions (`__anext__`, `__aenter__` and `__aexit__`) so would have been better named something like `for await` and `with await`. `async def`, on the other hand, was just copied cargo-cult-style from C#. I don't think it's necessarily bad, but you're right to say that it's inconsistent with Python's existing generators.
- BiteCode_dev 6y agoI have the opposition opinion. I think generators should have had an explicit keyword for defining them, like: gen def stuff() Because it doesn't, people are very confused about generators, and think they are functions, which they are not. They are completely different objects. Plus there is no way to tell if a definition is a generator without reading all of it. Explicit is better than implicit and all that.
- olau 6y agoWriting multi-threaded programs is much, much harder than single-threaded code. I think part of the attraction of async/await is that you can write mostly non-concurrent code. But then it gets extremely important that you're aware when you might have concurrency. For instance: x = get_something_from_global_state() y = foo() insert_into_global_state(x, y) If the program is suddenly possibly concurrent when calling foo(), then perhaps x is no longer valid. Good luck debugging that. await foo() makes it much more obvious. Now, of course, in scenarios where you don't really care about this property, e.g. if you're dealing with a database in another process where it is obvious that stuff may happen behind your back, or if you don't actually have any concurrency, I get that await is probably pretty annoying.
- peterkelly 6y agoIf all you're ever going to do with the result of an async function is await it immediately after calling, then you don't need async functions because you're just doing synchronous programming with extra syntax. The reason there's a difference between foo() and await foo() is that the former gives you a future (aka Promise in JavaScript), while the latter gives you the result. You can then wait on multiple futures, so that things can happen concurrently. If you add type annotations to your code and use mypy (which you definitely should), it'll pick up the distinction between the two types - Awaitable[str] vs str - and give you an error if you try to pass the future object to a function that expects a string. The only case where I've run into problems is where I've called an async function that returns None, but have forgotten to await the result. In that case the function never actually begins execution.
- BiteCode_dev 6y ago> How do you know how to call that function? async def foo() is not a function and it doesn't have the same signature: the async is very explicit. It's a different primitive, and it's not used AT ALL for the same thing at a function. It's like writting: def foo(): yield "bar" and saying, 'how do you cal that function? How do you know how to call that function? It's called the same thing. It has the same signature.. It returns the same thing. One has one exit point, the other has several. This is "colored functions". You have to call the 2nd one like: next(iter(foo())) It's absurd' Well, no, it's not. It's a different tool. It's not a function, it's a generator. You don't have to care about async/await. In fact, you can live your entire life without using them. But it's a handy tool that allows you do: - delegate asynchronous behavior, like I/O, outside of your callable - make you match the syntaxic order with execution order despite said delegation - make it clear and explicit where you chose to do so, indentifying the context switching of your program Does it seems complicated ? It's because it is, and you probably don't need it. But if you need it, it's easier than the alternative of using callbacks, and less error prones that implicit asyncronicity.