A crash course in Python “comprehensions” and “generators”
medium.com
medium.com
They're so powerful in fact, that asyncio is implemented under the hood with a lot of generator cleverness. You rarely see it, but the `__await__` dunder method which powers `await` is just a synchronous function which returns a generator.
Generator expressions are useful for when there's memory constraints that preclude using a list comprehension, but list comprehensions are actually faster because of cPython's internal optimizations.
[0] https://peps.python.org/pep-0255/ [1] https://peps.python.org/pep-0289/
I once wrote a library of signal processing components based on the premise that the raw input signal is such a sequence, and its concrete type was Iterable[np.ndarray] (iterable of chunks of audio signal). Since the signal is unbounded - we just continue sampling from, say, a microphone forever - the types can't involve finite sequences like lists. A general iterable is works here, and then components that transform the sequence are themselves implemented as generators, or if they are very simple, as generator expressions.
Consider the following function for amplifying a signal:
def amplify(signal: Iterable[np.ndarray], a: float):
return (a*chunk for chunk in signal)
You can flip the whole infinite streams interface and talk about "pushing" chunks around, and it's an esthetic choice.I'm dumbfounded that I have written python for as long as I have without ever encountering this, TIL!
https://lerner.co.il/2020/05/08/making-sense-of-generators-c...
> "The above code looks a bit weird, in that “yield’ is on the right side of an assignment statement. This means that “yield” must be providing a value to the generator. Where is it getting that value from?
Answer: From the “send” method, which can be invoked in place of the “next” function. The “send” method works just like “next”, except that you can pass any Python data structure you want into the generator. And whatever you send to the generator is then assigned to “x”."
Regarding that article specifically I think it could've done better service to 'yield from' had it mentioned recursive generators. One example would be flattening nested lists: have a flatten function that checks if its argument is iterable: if not just yield the thing else yield from flatten for every item in the iterable.
https://twisted.org/documents/21.2.0/api/twisted.internet.de...
Imo it’s a well written, technically accurate article.
Complicated comprehensions are not clever or impressive, they’re annoying.
Haskell list comprehensions limited to two clauses, SQL queries limited to a single FROM and a WHERE, or a FROM and a JOIN sound pretty limited. I use Python comprehensions to do those same kinds of data querying without leaving the Python context, so seems weird to limit myself for the same reason.
Using imperative constructs certainly isn't wrong, but they tend to crawl off the right hand side of the page.
I suppose this is only true in combination with verbose function names that capture the logic of the operation you're performing, with the comprehension expressing how this logic is composed. In my mind, comprehensions are sugar over map(), filter(), etc., and before I was using comprehensions I had a mess of nested calls to these functions with lots of lambdas. Comprehensions are a big step up from that as far as readability goes.
americans := SELECT username FROM users WHERE country = "us";
SELECT url, visits FROM posts, americans ON posts.author = americans.username ORDER BY visits DESC LIMIT 10;With americans as (select ...) select from ..., americans ...
https://www.draxlr.com/blogs/common-table-expressions-and-it...
It could work as an expression which gets injected into the 2nd query. So the second query would effectively have a nested SELECT statement as defined by the americans expression.
But this has the downside that if you reuse americans you have to know that it will be recomputed at each query. So unless you know the semantics of these variable-expression things over time you get race conditions. The solution again would be to wrap everything in a transaction, but at that point you just have to constantly think about transactions, which you don’t if your query is a one-liner.
Also good luck writing a query planner that has to efficiently take into account variables.
But it was completely incomprehensible and unmaintainable. So got replaced by me later.
Instead of writing for loops he would write comprehensions everywhere because "list comprehensions are pythonic".
Think like:
[send_welcome_email(u) for u in users]
Just hanging out in the middle of the module or function.sent = [(u, send_welcome_email(u)) for u in users]
not_sent = users - {u for u in users if send_welcome_email(u)}
Just make sure it isn't consuming a lot of RAM and it's totally OK.
sum(
a + b
for a in range(10)
if a % 2 == 0
for b in range(10)
)On the other hand, and maybe is me being too literal, I think it gives the wrong impression about what a generator is. It has nothing to do with one liners using brackets. My generators use 'yield' and look like a 'for' and they are still generators.
Incredible it has taken 40+ years for some languages to just catch up to the concept!
from itertools import chain
list(chain(*trees))
list(chain.from_iterable(trees))
list(chain(*dog_breeds.values()))
list(chain.from_iterable(dog_breeds.values()))I usually resort to building up generator expressions, using itertools, or writing traditional loops.
$0.02.
WRT to length of a statement, look at how many operations are being done. If it’s doing something like filtering that should be 2-3 ops on the same line. Perfect application of a comprehension with an if statement.
If it’s 5+ different things jammed into a comprehension, you’re failing PR. Don’t think it needs to hit 2 LOC before refactoring.
Build a list, take a property, call a function with that value, iterate, if something, else something, all on one line?? Aren’t you clever. Now, let’s write professional code.
In a REPL session, "_" means "the previous output"
Wow TIL. After all these years. I would always just arrow up and go to the beginning of the line to bind to a variable if I missed it the first time. Nice to learn something new, however small!Another interesting little rabbit hole that this little conversation led me to: In the Chrome (and Firefox) developer tools, the variable for the previous output is "$_", which I imagine that is the case because of how common it was to assign the main export of the Underscore.js library to "_" (and in the days before a lot of websites would mostly e.g. have their site's code in a webpack-induced closure, they would e.g. grab underscore (or lodash, in those times?) from a CDN and pollute the global scope).
Since _ is a valid identifier, it also turns out that in Node.js's REPL, it warns you when you clobber _, but (weirdly) not if the clobbering is with a block-scoped declaration.
$ node
Welcome to Node.js v18.12.1.
Type ".help" for more information.
> "asdf"
'asdf'
> _
'asdf'
> var _ = 1
Expression assignment to _ now disabled.
undefined
> "asdf"
'asdf'
> _
1
$ node
Welcome to Node.js v18.12.1.
Type ".help" for more information.
> const _ = 1
undefined
> _
1
> "asdf"
'asdf'
> _
1
And obviously, there's no warning for assigning to it outside of the REPL (`node -e "var _ = 2;"`), which makes obvious sense to me.So, I recognize fold, from the above operations ('reduce' in python), what makes it a "functional transformation"?
I'm googling function/functional transformations, and the results refer to translating, flipping or scaling a function, like transforming f(x) to f(x + 2) or f(x) + 2. It doesn't seem like it has anything to do with what you guys are referring to, but correct me if I'm wrong.
Either its the course of treatment you get after a crash
Or it's the course of actions which is going to lead to a crash.
I should add I'm a serial offender.