We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Async/await in Python 3.5 and why it is awesome

00:00

Formal Metadata

Title
Async/await in Python 3.5 and why it is awesome
Title of Series
Part Number
80
Number of Parts
169
Author
License
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Identifiers
Publisher
Release Date
Language

Content Metadata

Subject Area
Genre
Abstract
Yury Selivanov - async/await in Python 3.5 and why it is awesome async/await is here, everybody can use it in Python 3.5. It's great and awesome, yet only a few understand it. As a PEP 492 author, I'd really like to have a chance to better explain the topic, show why async/await is important and how it will affect Python. I'll also tell a story on how I worked on the PEP -- starting from an idea that I discussed with Guido on PyCon US 2015, and landing to CPython source code one and a half moths later! ----- The talk will start with a brief coverage of the story of asynchronous programming in Python -- Twisted, Tornado, Stackless Python & greenlets, eventlet, Tornado, asyncio & curio. We've come a really long road, and it's important to understand how we ended up with async/await. Then I'll go over asyncio and curio, showing async/await by example, explaining that in reality it's a very easy to use language feature. You don't need to know all the details to be able to successfully use the new syntax, and even build new frameworks on top of it. I'll then explain the async/await machinery in CPython, starting with generators and 'yield' expression, showing what is 'yield from' and finally, demonstrating how async/await is implemented in CPython. This will ensure that those who want to invent some new crazy ways of using async/await will have a starting point! I'll end the talk with a story of how I came up with the idea. How I shared it with Guido van Rossum, Victor Stinner, and Andrew Svetlow. How the first version of the PEP was born, and how we managed to push it to Python 3.5 in under two months period. The goal is to make people understand that it's possible to change your programming language -- in fact, Python, as any other programming language, wants new features and capabilities to be relevant.
11
52
79
Core dumpSoftware developerLecture/ConferenceMeeting/InterviewComputer animation
Core dumpSoftware developerPauli exclusion principleWebsiteStack (abstract data type)BitSurgeryMultiplication signSingle-precision floating-point formatSoftware testingSurvival analysisComputer programmingAuthorizationParallel computingElectronic signatureWeightAlpha (investment)Pauli exclusion principleCoroutineCore dumpSoftware developerLecture/ConferenceMeeting/InterviewComputer animation
Library (computing)Event horizonGreen's functionElectric generatorSinc functionExpressionLimit (category theory)CoroutineLecture/Conference
WeightExpressionElectric generatorStatement (computer science)Functional (mathematics)Limit (category theory)DemosceneSemantics (computer science)Source codeInstance (computer science)Single-precision floating-point formatException handlingExtension (kinesiology)Sinc functionCoroutineFrictionComputer animation
ResultantRevision controlFunctional (mathematics)Control flowElectric generatorQuery languageSoftwareEqualiser (mathematics)NeuroinformatikSystem callComputer programmingLecture/ConferenceComputer animation
Source codeCore dumpElectric generatorCoroutineLecture/Conference
Data typeSystem callFunction (mathematics)Formal languageDemosceneWeightMechanism designPoint (geometry)Frame problemFunctional (mathematics)Software frameworkMultiplication signNeuroinformatikElectric generatorDifferent (Kate Ryan album)CoroutineConstructor (object-oriented programming)Computer clusterType theoryComputer animation
Profil (magazine)ResultantMultiplication signCoroutineNormal (geometry)System callExtension (kinesiology)Local ringWeightFunctional (mathematics)Lecture/Conference
LogicCoroutineProcess (computing)Interpreter (computing)Electric generatorWeightQuicksortLecture/Conference
Data typeSource codeShared memoryEqualiser (mathematics)Electric generatorGroup actionDemosceneImplementationDirected graphSynchronizationContext awarenessData managementObject (grammar)Type theoryCoroutineCommunications protocolCodeTerm (mathematics)OpcodeComputer animation
Real numberObject (grammar)CodeReal numberWeightInformation securityStatement (computer science)Database transactionBlock (periodic table)MereologyVideo gameSystem callDatabaseFerry CorstenLecture/ConferenceComputer animation
Real numberImplementationDatabaseDevice driverFunctional (mathematics)Query languageDirected graphCursor (computers)IterationLecture/ConferenceComputer animation
Software developerMereologyStandard deviationPort scannerSoftware frameworkBitTerm (mathematics)DatabaseDevice driverImplementationLibrary (computing)MereologyStandard deviationInstance (computer science)Directed graphFrame problemLecture/ConferenceComputer animation
Decision theoryGoodness of fitStability theoryLibrary (computing)Multiplication signBuildingWhiteboardDifferent (Kate Ryan album)Finite differenceComputing platformPhysical systemPoint (geometry)Direction (geometry)MereologyLecture/Conference
Software developerMereologyStandard deviationPort scannerEvent horizonCodeCartesian coordinate systemComputer animationLecture/Conference
Event horizonInterface (computing)Server (computing)Task (computing)Queue (abstract data type)SynchronizationCodeImplementationConnected spaceStreaming mediaBridging (networking)Task (computing)Interface (computing)Server (computing)Event horizonCommunications protocolHeat transferLoop (music)Factory (trading post)WeightParameter (computer programming)Transportation theory (mathematics)Service (economics)Reading (process)Single-precision floating-point formatComputer animation
Queue (abstract data type)SynchronizationPrimitive (album)CASE <Informatik>Interactive televisionProcess (computing)Speech synthesisEvent horizonSemaphore lineLecture/Conference
Instance (computer science)Event horizonLine (geometry)Task (computing)Loop (music)CoroutineObject (grammar)Number2 (number)Functional (mathematics)ProgrammschleifeGame controllerSoftware frameworkCondition numberEqualiser (mathematics)SequenceComputer programmingSingle-precision floating-point formatWeightSystem callComputer animation
Functional (mathematics)Roundness (object)Task (computing)Event horizonLoop (music)BitCoroutineLecture/ConferenceComputer animation
DataflowSource codeProteinFunctional (mathematics)CoroutineLoop (music)Parameter (computer programming)CodeThread (computing)Process (computing)Lecture/ConferenceComputer animation
Software testingLoop (music)Parallel computingModule (mathematics)Loop (music)Functional (mathematics)Closed setVariable (mathematics)Inheritance (object-oriented programming)Integrated development environmentCodeComputer programmingAsynchronous Transfer ModeSoftware bugEvent horizonLecture/ConferenceComputer animation
Error messageComputer programmingModule (mathematics)Function (mathematics)Software-defined radioLecture/Conference
Letterpress printingInstance (computer science)Default (computer science)Ring (mathematics)Software testingConfiguration spaceRegular graphFunctional (mathematics)System callLecture/Conference
Software testingLoop (music)Event horizonTask (computing)Inheritance (object-oriented programming)Functional (mathematics)WeightSystem callLevel (video gaming)Computer programmingLattice (order)Structural loadFormal languageImplementationEvent horizonLoop (music)Exterior algebraLecture/ConferenceComputer animation
Goodness of fitLoop (music)ImplementationTask (computing)System callWeightLecture/Conference
Socket-SchnittstelleBenchmarkSoftware frameworkServer (computing)QuicksortBitMultiplication signWordProduct (business)Cartesian coordinate systemImplementationTheoryQuality of serviceTraffic reportingStructural loadLatent heatLoop (music)Lecture/ConferenceXMLUML
Pauli exclusion principleIdeal (ethics)Online helpServer (computing)ImplementationPauli exclusion principleParameter (computer programming)Formal languageLecture/ConferenceComputer animation
Right angleReduction of orderSound effectStandard deviationDatabase transactionCodeRollback (data management)Lecture/Conference
Pauli exclusion principlePrototypeIterationEmailPatch (Unix)ImplementationFreezingComplete metric spaceEmailProduct (business)Drop (liquid)RadiusPrototypePatch (Unix)Pauli exclusion principleLecture/ConferenceComputer animation
Process (computing)Software developerPauli exclusion principleData structureCore dumpType theoryLecture/Conference
Pauli exclusion principleMetric systemSign (mathematics)Instance (computer science)Operator (mathematics)Arithmetic meanImplementationComputer programmingLecture/Conference
Multiplication signBuildingContext awarenessData managementForcing (mathematics)Radical (chemistry)Event horizonLoop (music)JSONXMLComputer animationLecture/Conference
Loop (music)Data managementContext awarenessEvent horizonComplex (psychology)Closed setComputer programmingMetadataCausalityLecture/Conference
Lecture/Conference
Core dumpMathematical optimizationDrop (liquid)Lecture/Conference
Code refactoringCore dumpDecision theorySoftware developerSpeicherbereinigungCodeData managementAbstractionPhysical systemNeuroinformatikEqualiser (mathematics)Level (video gaming)Lecture/Conference
Scripting languageSoftware repositoryBenchmarkSoftware frameworkInstance (computer science)Traffic reportingLecture/Conference
Condition numberRow (database)Spring (hydrology)PreconditionerLecture/Conference
Information systemsRed HatLecture/ConferenceComputer animation
Transcript: English(auto-generated)
I'd like to introduce Yuri Zilivanov. He's a Python core developer and the main developer behind Async.io coming from Toronto, Canada to us. Giving a warm welcome, please.
So this talk is an introduction to Async Await and to Async.io. It's nothing like my previous talk. This talk is basically for beginners. So a little bit about me. I'm co-founder of Magic Stack. Check out our website, it's magic.io. I'm in love with Python users since 2008
and I believe, I just started to use Python 3 when it was alpha 2 and I never looked back, so use Python 3. I'm C Python core developer since 2013. I think I started to contribute a little bit earlier than that, but still. I co-authored with Brett Cannon and Larry Hastings,
PEP 362, that's inspect signature API. I authored and implemented PEP 492, that's Async Await syntax. I maintained Async.io with Guida and Victor Steiner and I also created UVloop and AsyncPG. Let's talk about coroutines in Python. So there are five obvious ways to do coroutines in Python.
The first one is callbacks and deferreds. Not exactly coroutines, but a way to do asynchronous programming. Twisted has been with us since 2002 and I believe that's the first time you could actually do asynchronous programming in Python reliably.
Then we had stackless Python and greenlets. I'm pretty sure everybody knows libraries like G event and eventlet. Those are great examples of how you integrate those features. Since Python 2.5, we could create coroutines using generators and yield expressions. Although there were some limitations with this approach,
first is performance and the second one is that you couldn't actually use return statement in generators. Since Python 3.3, we had yield from syntax and Async.io for instance uses that extensively. And in Python 3.5, we have Async Await.
So let's take a look how coroutines using yield expression look like. This is an actual example from twisted documentation. Old grumpy coroutine. But if you look at the source code, you will see that there is no return statement. That you have to kinda use a function to return. What this function does internally,
it rises an exception. And that's how this limitation that you cannot use return statement was overcome. Yield from coroutines, since Python 3.3, they look much better actually. It's recommended to use Async.io coroutine decorator,
but it's not required. And you have to use yield from. A lot of problems with this approach were that people actually didn't understand what yield from is and when to use it and then how it actually works. What do you have to put from? So we had some friction
with actually teaching people how to use that. And also there is a slight problem with generators in general. If you don't use this Async.io decorator, let's say you have the first version of your software with a coroutine called compute. And you compute something, you sleep for one second,
and then you return the result. And in version two, you decided to make your users happy and you removed the Async.io sleep call. And now your function doesn't have any yield from. So now it's not a coroutine, now it's not a generator. So your program just breaks. To ensure it never happens, you have to use Async.io coroutine.
But some people forget. And also when you look at a lot of source code and you see lots and lots of yield froms, you kind of stop understanding where are the coroutines and what are the generators and how they interact with each other. So it's all been a mess. And in Python 3.5, we introduced Async.await.
So we don't need any decorators now. Even if the function doesn't have any awaits inside, we know it's a coroutine because of the async keyword. So why do I think that Async.await is the answer how you do coroutines in Python? Well, first of all, first time in Python history we have dedicated syntax for coroutines. It's concise, it's readable,
it's easy to teach people how to use. We also have new built-in type for coroutines, again for the first time in Python history. It's not a subtype of generators anymore. It's its own thing. We also have AsyncFor and AsyncWith constructs.
And these, I believe, are kind of unique to Python. I know no other language that allows you to do that kind of magic. And Async.await is actually a generic mechanism. A lot of people think that Async.await is somehow tied to Async.io. It's not true. Actually, Tornado uses Async.await now.
Twisted is about to start using Async.await. And there is another framework called Curio which uses Async.await in an entirely different way from Async.io. And also Async.await is fast. If you write, let's say, a Fibonacci function and then time coroutines versus normal functions,
you will see that coroutines are only two times slower, which is totally fine, because even in large Async.io applications, you usually have 50, maybe 100 await calls per request, but you have thousands or hundreds of thousands of normal calls. So use Async.await extensively. Don't worry about it.
It won't show up in your profile results. And also, Async.await is faster. Async.await and yield from are faster, actually, than the old approach of using yield coroutines, because a lot of logic of how you actually resume and how you push values to coroutines
before had to be implemented in Python, and now it's the job of Python interpreters to do that, so it's much, much faster. So coroutines are subtypes of generators, but not in Pythonic sense. In Pythonic sense, generators and coroutines are completely different objects. They are subtypes in terms of C implementation.
They actually share the same C struct layout. They share a lot of code, like 95% code is shared. And it actually shows, if you disassemble a coroutine, you will see that it still uses yield from opcode. We also have types coroutine decorator that allows you to transform any generator into a coroutine, so it's compatible with Async.await syntax.
And we have a bunch of protocol methods for asynchronous generators and asynchronous context managers and future-like objects. You can read about all of that in PEP492. So how does Async.await code look in real life? It's quite easy, actually.
You can see that we have a serious coroutine, then we have an await call that prepares a database statement, then we enter a transaction. And here is an interesting part about Async.with, actually, it allows you to execute some asynchronous code when you enter the block and allows you to execute some asynchronous code when you exit the block. So it's quite important, actually,
to implement database drivers where you kinda need this functionality. And then you can see Async.for, which is also unique. It allows you to call asynchronous code while iterating. So in this example, actually, cursor prefetches you some data. You iterate over it efficiently,
and when you iterate over all prefetched data, it prefetches even more. So it's quite efficient to iterate over large data sets. Now let's talk about Async.io. So it's developed by Guido himself, at least initially. A lot of people think that Async.io is a framework. I think it's a little bit wrong term to use for Async.io.
I view it more as a toolbox, as a collection of tools for framework creators to use. For instance, Async.io doesn't have any HTTP implementation. It doesn't have any database drivers. You have to install libraries to do that. It's also part of standard library.
And this is both good and bad. It's good because everybody, if something ends up being in standard library, it will be supported. It will be supported until the end of time, basically. So it's a good and stable business decision to actually use Async.io. Python also has a huge collection of build boards for different platforms and different operating systems.
And it's quite important to actually test Async.io on all of them because I always is really hard. And another exciting point about Async.io is that Twisted soon and Tornado right now, they can actually integrate with Async.io. So in theory, you can actually run existing
Tornado applications on top of Async.io event loop. So the code reuse possibilities are just exciting. So what's inside Async.io? So we have standardized and pluggable event loop. You can actually swap the event loop implementation with something else, if you want. Async.io also defines a bunch of interfaces for protocols and transfers.
It has factories for creating servers and connections and streams. It also defines futures and tasks. Futures are essentially a bridge between old callback style code and new Async Await style code. And task is also something really fundamental.
So task is what we actually call coroutine runner. It's something that allows event loop to actually run coroutines, to suspend them, to push values into them and resume them. And then we have APIs to create and interact with subprocesses asynchronously. We have queues and we have synchronization primitives
like locks, events, semaphores, and other stuff. And Async.io is simple. This is actually a screenshot of Async.io documentation. It only takes you five days to read it and a couple of months to digest. And then you can actually use it.
So joking aside, we know that this is wrong. We will improve Async.io documentation pretty much soon. And I'm going to prove you actually that Async.io is simple. You only need to know about seven functions to use Async.io every day. You don't need all this stuff in Async.io. Again, that's a toolbox. That's for framework creators.
To use Async.io, you only need to know this. So let's go over those functions. The first one, the first function that you have to know is Async.io get event loop. It returns you an instance of the actual event loop. And you have to worry about event loop when you launch the program,
but then it actually disappears. You aren't required to know about what kind of event loop you have. Just use Async.io throughout your program. That's it. So you get an Async.io event loop. That's the first highlighted line. And then you call create task. So what create task does, it wraps coroutines
into those task objects that allow event loop to actually run them. So what we have here, we have a coroutine called say coroutine function, which waits for when number of seconds and then prints what. So what we do next after we get the event loop,
we create two tasks for the same coroutine function. So essentially it will be two different coroutines. One will say hello after half a second and another one will say world after one second. And the last line is loop run forever and what it does, it literally loops run forever
until you actually terminate it somehow, maybe with control C, maybe some other way. The next function, which is actually quite important, is Async.io gather. And what it allows you to do, it allows you to wait on several coroutines simultaneously and it actually returns when all of them are resolved.
So we modified our example a little bit. Now we wrap two tasks to say hello and to say world with Async.io gather and we use run until complete. That's another method of event loop.
It accepts tasks, futures, coroutines, and it basically runs the event loop until the task is complete. So here we will end the execution when both coroutines are finished. Another important function is loop run in executor. So if you have a bunch of old code, let's say, or that uses blocking IO
or you have some very computationally intensive code, you can actually run that code in a thread or in a process pool and await on it. So it's actually quite handy. And the API itself is pluggable.
If you pass none as a first argument, it uses the Async.io internal thread pool, but you can actually pass any kind of pool executor from concurrent features module. And the last very important function is loop close. So all of our previous examples were kinda wrong
because we didn't close the loop. So the correct way is to actually run event loop and then finally block, you have to close it. The close function, close method, it actually cleans up the resources and it will print out warnings if something goes wrong.
And how, especially when you start learning Async.io, you should always use Async.io debug mode. To enable it, you can just have an environment variable called Python Async.io debug, or you can enable debug more programmatically, just call loop set debug.
It's very important to use Async.io in debug mode. It will uncover a lot of bugs in your code and it will make programming with Async.io much easier. Also, make sure that logging in Python is configured properly and you can actually see the errors in ECD out or SDR.
Because sometimes it's not and people just ask questions like I don't see any errors in program output. Async.io uses logging module, so if it's not configured properly, you won't see anything. And also, configure a test runner to print warnings. For instance, Pytest somehow doesn't do that by default.
And warnings are actually quite important. For instance, if you have a coroutine and you've just forgot to put a wait, you just use the regular function call. Then essentially, your program will do nothing because without a wait, it will just create a coroutine and that's it. It will never be scheduled. So Python is actually emitting a resource warning
if a situation like this happens. So make sure you actually see warnings. Let's quickly talk about UV loop. UV loop is an alternative implementation of Async.io event loop.
It's written in Cython and by the way, I really recommend you all to check out Cython. It's an amazing language. It looks like Python, but it compiles to C. It uses libuv under the hood and libuv is the event loop for Node.js and that's a good thing because Node.js is extremely widespread. It's well tested and libuv is really fast.
And also, UV loop defines its own implementation of futures and tasks so that even if you don't do a lot of I.O., your async await calls will just become faster. So how fast is UV loop? It's in a simple benchmark like an echo server.
It's actually quite a bit faster than Async.io. It's two to four times faster. If you have lots and lots of Python code, it won't be as exciting, but I heard a lot of reports that UV loop, even for big production applications, can give you a boost of 30, 40%.
And even if you don't have a high load on your server, I still recommend you to use UV loop just because the quality of service improves. You will have smaller latencies. So it's faster than Async.io, but it's also faster than any other Python framework on Python 3 specifically.
We are not talking about PyPy, twist, all this stuff. On Python 3, UV loop and Async.io are the fastest and the simple echo server implementation, it's actually as fast as Go echo server implementation. And it's somehow twice as fast as Node.js echo server implementation, which I personally find amazing because, well, Node.js itself uses LibUV
and itself is written mostly in C++ and C. Let's quickly talk about PEP 492. So I actually had this idea in 2014 and I quickly and briefly discussed it with Guido and Victor Steiner,
but we didn't have good arguments to actually think about this idea even more. But in 2015, I approached Guido at the language summit and I told him, let's do async await. People really don't know how to use yield froms. They don't understand that. And also, just besides doing async await,
let's introduce async for and async with because otherwise it's just impossible to write good looking code for entering a database transaction. You have to use try, finally call and rollback and manually call commit. And people just don't know how to do this. So it's not convenient. So actually, he liked this idea
and he encouraged me a lot because it was just two months before complete feature freeze for Python 3.5 release. So I spent about two, maybe five days for the first draft of the PEP. About three hours to prototype and see Python. And then I published my first draft on Python ideas.
And it was very, very positively received. So after about 500 emails and Python ideas and Python dev, we had a PEP that everybody liked. And then Nick Coghlan and Victor Steiner helped me with reviewing the patch and actually pushing it in production.
So why am I telling you all this? Is that I want you to do the same. I want you to actually, if you want something to do in C Python, some new feature, go ahead and propose it to Python ideas. Before that, please Google that this feature wasn't proposed before. And if it wasn't, just go ahead and do it.
The process from that is quite simple. You will discuss it on Python ideas with other Python users and Python core developers. If they like this idea, they will probably ask you to write a PEP, which is also quite an easy thing. We have hundreds of PEPs available now. You can read through them. You can see the common structure.
So you document your feature idea. And then there is another process. You will discuss this PEP. Then you implement this PEP or you can find some other core developer who will do that for you. For instance, the metrics multiplier operator, the at sign was added to Python 3.5.
The PEP initially was written by Nathaniel Smith, but the implementation was written by Benjamin Patterson. So you don't even need to know low-level C Python programming. If you have an exciting idea, somebody will implement it. That's it. Thank you.
Thank you very much for this talk. We have time for questions. If they are building context manager versus try-finally event loop termination thing, you showed, you say the try-finally terminate event loop.
Is there a context manager for this? No, we don't have context manager. But it would make sense to have a context manager for this? Maybe, but you use it only once in your program, usually. So that's why we are thinking that maybe it's not worth adding extra complexity to how you work with event loop. Because you can, actually,
you can only close event loop once. Once you close it, you cannot resume it. So if you use this context manager twice, it will break your program. So we decided that let's make it explicit. All right.
In AsyncIO? Maybe, yeah. Yeah, if UVloop is much faster and a drop-in replacement for AsyncIO,
why don't we just replace AsyncIO in the core Python with UVloop rather than force the users to, ask the users to replace it as an optimization? So the question is, why don't we just put UVloop in CPython, right?
There are a couple of problems with that. First of all, UVloop is written in Cython. We don't merge anything written in Cython to CPython just because we don't want to introduce extra build dependencies in CPython. So to do that, we'll have to rewrite it in C. I think it's quite doable, actually,
because when I was working on UVloop, it was actually hard to figure out how to correctly manage libuv low-level resources and Python high-level abstractions together so that garbage collection works correctly, stuff like that. I think now I actually have pretty good understanding
how to write the system in pure C, but before that, it just wasn't feasible just because it's much easier to refactor code in Cython. So to merge UVloop or to recreate it in CPython, we will need to write it in pure C and also have to make a decision of making libuv a dependency of CPython,
which probably a lot of other core developers will have some concerns with. But I think maybe eventually we'll do that. Hi, is it possible, have you published your benchmark scripts that you've actually?
They're all available at GitHub. It's a repo called VMbench. It's on the same account as UVloop. Everything is there. Benchmarks are quite generic. You have a script to run them all, and what they actually do, they launch a Docker instance,
and they launch each framework there. They warm up there in that Docker instance. So it's all automated. So you definitely can do it yourself. You can run them. Hi. Regarding the documentation issue, so if you're gonna hold a sprint on that topic,
I would gladly contribute for the basic examples because I'm really struggling with what's currently available. Sure, I would really appreciate it. I'm not sure if I will be on the sprints. If I'm not, just create an issue at Python Backtracker,
and I'll definitely take a look. Any more questions? Okay, I think that's it. Then thank you very much again. Thank you guys. Thanks.