Execution starts from the first line and continues until
Object capable of returning it's members one at a time
Can be used in for, zip, map, etc.
An object representing a stream of data
Repeated calls to the iterator’s __next__() method (or passing it to the built-in function next()) return successive items in the stream
When no more data is available a StopIteration exception is raised instead
Iterators are required to have an __iter__() method that returns the iterator object itself
>>>examples
>simple_iterator.py
A generator generates values
Generator function
We can get the values from a generator by calling next
>simple_generator.py
Generator function creates generator iterator a.k.a. generator
Once a generator has been exhausted it will raise StopIteration
You can only ever consume all values from a generator once
.send(value) - resumes execution & passes value into the generator
Simplified - calling .send(None) is equivalent to calling .next()
>>>example
>complex_generator.py
`send` returns the next value yielded by the generator
Execution begins at the top of the generator's function body
There is no yield expression to receive a value when the generator has just been created
yield from <expression>
The expression must evaluate to an iterable, from which an iterator is extracted and run until exhaustion
When the iterator is another generator, the subgenerator is allowed to execute a return statement with a value, and that value becomes the value of the yield from expression
>yield_from.py
Computer program components that generalize subroutines for nonpreemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations
Functions whose execution you can pause
They can be
They don't execute until you tell them to
Global interpreter lock
It ensures that only one thread runs in the interpreter at once
You can't utilize multiple CPUs
1) You do not talk about the GIL
2) You do NOT talk about the GIL
Programming construct that waits for and
dispatches events or messages in a program
Maximize the use of single thread
You can't achieve parallelism
One event loop calling callbacks
In Python 3.5, the `types.coroutine` decorator was introduced
A coroutine function may be defined with the async def statement, and may contain await, async for, and async with keywords.
An object that can be used in an await expression can be a coroutine or an object with an __await__() method
It transforms a generator function into a coroutine function which returns a generator-based coroutine. The generator-based coroutine is still a generator iterator, but is also considered to be a coroutine object and is awaitable
It takes coroutines from simply being an interface to an actual type, making the distinction between any generator and a generator that is meant to be a coroutine much more stringent
`await` expression is only valid within an `async def`
An object that defines an __await__() method which returns an iterator which is not a coroutine itself
Coroutines themselves are also considered awaitable objects
When you call await on an object , it needs to be an awaitable object
Defining a method with async def makes it a coroutine
An await expression is basically `yield from` but with restrictions of only working with awaitable objects (plain generators will not work with an await expression)
An async function is a coroutine that either has:
The other way to make a coroutine is to flag a generator with types.coroutine
async/await is really an API for asynchronous programming
asyncio is a framework that can utilize the async/await API for asynchronous programming