Showing 50 out of 95 links. See a condensed link blog feed.
State machines are wonderful tools (via) by Chris Wellons on 05-09-2025 13:37
In this article, Chris shares a couple of examples of using state machines for some tasks (decoding Morse and UTF-8) and claims that state machines are โwonderful toolsโ for a variety of reasons.
However, what caught my attention the most was a Python generator that counted words in a stream of bytes passed into the generator via .send
:
WHITESPACE = { # Codepoints for whitespace characters.
0x0009, 0x000a, 0x000b, 0x000c, 0x000d,
0x0020, 0x0085, 0x00a0, 0x1680, 0x2000,
0x2001, 0x2002, 0x2003, 0x2004, 0x2005,
0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
0x2028, 0x2029, 0x202f, 0x205f, 0x3000,
}
def wordcount():
count = 0
while True:
while True:
# low signal
codepoint = yield count
if codepoint not in WHITESPACE:
count += 1
break
while True:
# high signal
codepoint = yield count
if codepoint in WHITESPACE:
break
wc = wordcount()
next(wc) # prime the generator
wc.send(ord('A')) # => 1
wc.send(ord(' ')) # => 1
wc.send(ord('B')) # => 2
wc.send(ord(' ')) # => 2
First, Chris rightfully claimed that the fact that you have to use .send
to pass bytes into the generator made it painful to work with. And second, Chris seemed to enjoy branchless implementations of state machines, so I tried to make the generator branchless and easier to work with:
WHITESPACE = {...}
def wordcount(source_stream):
count = 0
in_word = False
for codepoint in source_stream:
count += (not in_word) and (codepoint not in WHITESPACE)
in_word = codepoint not in WHITESPACE
yield count
s = "A B "
for char, count in zip(s, wordcount(map(ord, s))):
print(count) # 1 1 2 2
Depending on whether you'll be getting the data from strings, streams of bytes, files, etc, and depending on whether you'll want just the final word count or a stream of byte & count pairs, you can write a tiny wrapper to make it even easier to work with.
For example, assuming you'll be giving it non-empty strings and want only the final count:
from collections import deque
# wordcount implementation.
def string_word_count(s):
return deque(wordcount(map(ord, s)), maxlen=1).pop()
print(
string_word_count("The quick brown fox jumps over the lazy dog.")
) # 9
Cyclopts on 30-08-2025 16:48
Cyclopts is โa modern, easy-to-use command-line interface (CLI) framework that aims to provide an intuitive & efficient developer experience.โ The idea behind Cyclopts is also to โwhat you thought Typer wasโ. Although that sounds like a dig on Typer, I guess you can just interpret it as a statement in favour of Cyclopts's features, like making use of docstrings and supporting more complex types like literals and unions.
Here is an example of a tiny Cyclopts application taken from the docs:
import cyclopts
from typing import Literal
app = cyclopts.App()
@app.command
def deploy(
env: Literal["dev", "staging", "prod"],
replicas: int | Literal["default", "performance"] = "default",
):
"""Deploy code to an environment.
Parameters
----------
env
Environment to deploy to.
replicas
Number of workers to spin up.
"""
if replicas == "default":
replicas = 10
elif replicas == "performance":
replicas = 20
print(f"Deploying to {env} with {replicas} replicas.")
if __name__ == "__main__":
app()
Here's what it looks like:
$ my-script deploy --help
Usage: my-script.py deploy [ARGS] [OPTIONS]
Deploy code to an environment.
โญโ Parameters โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ * ENV --env Environment to deploy to. [choices: โ
โ dev, staging, prod] [required] โ
โ REPLICAS --replicas Number of workers to spin up. โ
โ [choices: default, performance] โ
โ [default: default] โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
$ my-script deploy staging
Deploying to staging with 10 replicas.
$ my-script deploy staging 7
Deploying to staging with 7 replicas.
$ my-script deploy staging performance
Deploying to staging with 20 replicas.
$ my-script deploy nonexistent-env
โญโ Error โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ Invalid value for "ENV": unable to convert "nonexistent-env" โ
โ into one of {'dev', 'staging', 'prod'}. โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
$ my-script --version
0.0.0
From Wikipedia, โMemetics is a theory of the evolution of culture based on Darwinian principles with the meme as the unit of culture. [...] All evolutionary processes depend on information being copied, varied, and selected, a process also known as variation with selective retention.โ.
I may be getting this completely wrong, but sounds like what's being posited here is that the culture around memes evolves according to Darwinian principles... And it kind of makes sense!
Draft SMS and iMessage from any computer keyboard by Seth Larson on 29-08-2025 14:34
This small tool allows you to write a text message on your computer. As you type, a QR code on the right of the page is being generated to encode the text message you want to send. Once you're done typing on your computer you scan the QR code with your phone and it automatically opens the โmessagesโ app on your phone with the text and recipient(s) you typed.
Python: The Documentary | An origin story - YouTube on 28-08-2025 11:47
Wonderful documentary starring lots of influential people, telling the origin story of Python and its evolution over the past ~35 years, including accounts of its influence in the scientific community, the hurdles that Python faced when switching from 2 to 3, and its commitment to fostering an inclusive community.
Crimes with Python's Pattern Matching by Hillel Wayne on 22-08-2025 22:16
In this article, Hillel abuses using __subclasshook__
in abstract base classes when used in conjunction with structural pattern matching.
As an example, define
from abc import ABC
class NotIterable(ABC):
@classmethod
def __subclasshook__(cls, C):
return not hasattr(C, "__iter__")
Now, you can use NotIterable()
in a match ...: case ...:
to match non-iterable values:
def wtp(value):
match value:
case NotIterable():
print("Not an iterable.")
case _:
print("Iterable.")
wtp(3) # Not an iterable.
wtp([]) # Iterable.
Another thing I learned from this article is the English noun chicanery, โthe use of deception or subterfuge to achieve one's purposeโ.
Best Code Rule: Always Separate Input, Output, and Processing (via) by Volodymyr Obrizan on 22-08-2025 22:12
This article explains that the best way to write scripts in a way that sets you up for success is by separating the logic for
into three separate parts of your script. By separating these three components, it becomes easier to change them (for example, you're now reading from an API instead of from a file) when your requirements change. If things are tightly coupled, editing/updating/scaling becomes really hard.
I might add that separating these three components also makes it easier to turn your script(s) into CLIs.
Explicit method overriding with @typing.override by Redowan Delowar on 18-08-2025 13:33
This article is where I first learned about typing.overrides
, a decorator you can use to flag all method overrides when you're subclassing.
A method that is tagged with typing.overrides
is assumed to be overriding a method from a super class and if the type checker doesn't find the original method you're overriding, it'll let you know there's an issue with your code (maybe a typo? maybe the method changed name?).
As Redowan shows, overrides
also works with properties, class methods, and more.
Here's an example where mypy will complain because you forgot to specify that Cat.species
is a property:
from typing import override
class Animal:
@property
def species(self) -> str:
return "Unknown"
class Cat(Animal):
@override
def species(self) -> str:
return "Catus"
Email is Easy (via) by Sam Who on 18-08-2025 11:46
This 21-question quiz tests you on your knowledge about what comprises a valid email address. Play this to learn about all sorts of weird things regarding email addresses...
Prime Grid (via) by Danny Duplex on 15-08-2025 23:08
In the author's words, โPrime Grid creates a simple, adjustable grid that plots prime numbers in a left-to-right, top-to-bottom layout. Use it to find interesting visual patterns, do some smart-person math stuff, or maybe find the secret code to the cosmos or whatever.โ
What I found really cool about this very minimalistic grid is that I didn't expect any patterns to arise, but you can definitely see visual patterns when you play around with the grid dimensions. For example, if you set the grid to 205 x 209, what are those two wide empty spots along the main diagonal of the grid?! (I'm sure there is a good maths explanation, but it's not obvious to me what it is.)
Jevons paradox - Wikipedia on 11-08-2025 12:04
I learned about the Jevons paradox at PyCon Portugal from Pablo Galindo Salgado apropos something I can't remember. Initially observed in the 19th century, when the steam engine became much more efficient, folks expected coal usage to decrease โ after all, the engine was now much more efficient. Instead, overall coal usage increased because the better efficiency lead to an increased adoption of the steam engine. This increased adoption of the steam engine outweighed the increased efficiency, meaning the net effect was that more coal was being consumed.
Pybites #195: Patterns, paradigms, and pythonic thinking with Rodrigo Girรฃo Serrรฃo on 06-08-2025 13:42
My interview at the Pybites podcast, where I talk about Python, decorators, itertools
, giving talks at conferences, how ALP shaped my Python code, and more.
EPADEL March 29th Puzzle by David Nacin on 06-08-2025 12:41
A fun Sudoku variant puzzle created by someone I met at EuroPython. All Sudoku rules apply, plus:
The original link contains a harder puzzle, if you like solving this one.
EuroPython 2025 Recap by Cheuk Ting Ho on 25-07-2025 12:32
This article by Cheuk mentions some of the things that Cheuk enjoyed the most about the EuroPython 2025 conference. At the time of writing this, I haven't written the article with my personal highlights of the conference but I share one of the main sentiments that Cheuk expresses, which is that EuroPython would not be possible without the help of the 100+ volunteers who pour that heart and soul into the conference during the months leading to the conference, during the week of the conference, and for some, even after the conference.
Efficient streaming of Markdown in the terminal by Will McGugan on 24-07-2025 11:11
In this article, Will writes about how he implemented efficient Markdown streaming for his project Toad.
The highlight of this article, for me, is the fact that Will managed to implement efficient rendering of streamed Markdown content even though he's using a markdown parser that does not support streaming. His solution was simple in hindsight: Markdown can be parsed in blocks, and when you get content that starts a new block, you know you won't have to reparse the previous content.
Underused Techniques for Effective Emails ยท Refactoring English by Michael Lynch on 22-07-2025 22:54
In this post, Michael shares 5 techniques to write more effective emails:
The point about delivering the most important information first makes sense and is akin to the inverted pyramid scheme that journalists tend to use, where the info that is more relevant to more people is presented first. Then, as you keep reading, you get to the pieces of information that are relevant to less and less people.
Solving Wordle with uv's dependency resolver on 08-07-2025 12:01
In this article, the author solves Wordle by encoding the game information as Python package versions and then using uv's dependency resolver figure that out.
The very summarised gist of it is that different packages represent different types of information and then their versions encode the letters that go where. Then, the author created all the necessary fake packages locally (around 5,000 of them) and then got the dependency resolver to play the game by trying to resolve the dependencies of these fake packages.
Fixing Python Properties by Will McGugan on 26-06-2025 15:50
In this article, Will shares a situation he encountered when working on Textual, where if a property has setters and getters with different types, mypy will complain1. The example Will gives is for setting CSS padding, where the user can conveniently set the padding in 4 different ways:
However, the getter will always return a 4-item tuple with the individual values, even if they're the same.
Will's solution is to use a descriptor, which he presents, and then links folks who aren't familiar with descriptors to a talk of mine where I teach descriptors, which is a full-circle moment because I learned about descriptors by working on the Textual codebase.
What I enjoyed was that the code example that Will shares, that takes user-specified padding in any of the 4 formats and then homogenises it, can also be written neatly with structural pattern matching:
def unpack_padding(pad) -> tuple[int, int, int, int]:
match pad:
case int(p) | (int(p),):
return (p, p, p, p)
case (int(vert), int(horz)):
return (vert, horz, vert, horz)
case (int(top), int(right), int(bottom), int(left)):
return (top, right, bottom, left)
case _:
raise ValueError(f"1, 2 or 4 integers required for padding; got {pad!r}.")
As it turns out, this is no longer the case because mypy was updated recently to fix this.ย โฉ
The fastest way to detect a vowel in a string (via) by Austin Z. Henley on 24-06-2025 22:07
In this article, Austin goes over 11 creative ways to check if there is a vowel in a string.
After a lot of acrobatics and creative snippets of code, Austin found out that a regex search using the module re
was faster than any other Python solution that Austin wrote...
And then the internet stepped in, and the (so far) fastest solution was found:
def loop_in_perm(s): # best
for c in "aeiouAEIOU":
if c in s:
return True
return False
What's fun is that a similar function, with โthe loops reversedโ had already been considered and was only performing mediocrely. This is the mediocre version:
def loop_in(s): # similar to best but mediocre
for c in s:
if c in "aeiouAEIOU":
return True
return False
How Stanford Teaches AI-Powered Creativity in Just 13 Minutes on 23-06-2025 15:42
In this video, Jeremy Utley from Stanford shares some insights on how he teaches students to work with AI.
Maybe one of the most fundamental ideas he shares is that it helps to think of LLMs as โteammatesโ when interacting with them. For example, when an LLM produces a mediocre result, you shouldn't dismiss it. Instead, you can act as you would if that had been produced by a teammate: you give feedback and suggestions for improvements, which tends to lead the LLM to produce better results.
Another idea I liked from the video is a seventh grader definition of creativity. According to an anonymous seventh grader, creativity is doing more than the first thing you think of, and then Jeremy proceeds to building on top of this idea and saying that with LLMs it is now easier than ever to arrive at โgood enoughโ on the first try, and if you are aiming for excellent you need to fight against the tendency to settle for the first result you get.
GitHub - hugovk/em-keyboard: The CLI emoji keyboard (via) on 31-05-2025 15:42
This CLI written in Python lets you work with emoji from the comfort of your terminal.
You can get emoji by name and it's automatically copied to your clipboard:
$ em rocket
Copied! ๐
You can get the emoji but not copy it if you want to use it in scripts:
$ em "chocolate bar" --no-copy
๐ซ
You can also search for emoji by colour:
$ em -s brown
๐ค brown_heart
๐ด horse_face
๐ fallen_leaf
๐ man_s_shoe
๐ค brown_circle
๐ซ brown_square
๐โ๐ซ brown_mushroom
If your search query only returns one result, it's automatically copied to the clipboard as well:
$ em -s portugal
Copied! ๐ต๐น flag_portugal
@gutendelight | Botwiki on 31-05-2025 15:11
Today I was chatting with Hugo van Kemenade and he told me about a couple of โuselessโ bots he developed a while ago.
The deployment of these harmless bots had all sorts of curious repercussions and I hope Hugo shares these stories with the Internet (maybe through a blog article or a lightning talk at a PyCon conference), so that you can also have a good laugh like I did!
One of the bots that Hugo mentioned took hip hop lyrics and matched them with sentences from the Project Gutenberg, and this link should show you some samples as entertaining as this one:
โRoutines I bust, and the rhymes that I write
But ever Sir Gareth defended him as a knight.โ
Reinvent the Wheel (via) by Matthias Endler on 30-05-2025 22:41
This article resonated a lot with me because Matthias is advocating for an idea that I also support 100%: the idea that you should reinvent the wheel for the sake of improving your own understanding of things.
In this article, Matthias goes on to provide a series of strong arguments in favour of reinventing the wheel from time to time, but for me two key ones really are the fact that building things from scratch is:
I preach these two ideas a lot but I also practice them.
For example, roughly two weeks ago I was at PyCon US 2025 giving a tutorial whose main premise was that you'd learn a lot about iteration if you tried to reimplement the module itertools
.
Make a cutting room floor (via) by Trey Hunner on 22-05-2025 19:04
In this article, Trey talks about his writing process and what he calls his cutting room floor. Trey writes in three phases:
When editing, Trey often finds it hard to delete paragraphs/sections/code/... that he already wrote but that no longer fit well within the full picture. Instead of deleting everything outright, Trey cuts those sections into a separate document he calls the cutting room floor. By moving the content there, Trey can easily revive it or adapt it if need be; it's also easier to move text to a different document than it is to delete it.
After reading this article by Trey, I'm wondering if it would make sense for me to create a sort of cutting room floor, but then let the pasted contents stay there as possible inspiration for future content.
Python's new t-strings by Dave Peck on 19-05-2025 07:27
Python 3.14 is getting (or got, depending on when you read this) a new feature called t-strings. t-strings were introduced in PEP 750 and one of the PEP authors (Dave Peck) wrote a short article showing off some of the capabilities of t-strings.
One of the key points is that t-strings... are not strings!
Their type is string.templatelib.Template
.
t-strings are composed of string segments and the interpolated values (string.templatelib.Interpolation
), which will be whatever we put inside the {}
.
The programmer is then responsible for (defining and) using a function that processes the t-string into a string.
The simplistic example below escapes the characters <>
before interpolating HTML:
from string.templatelib import Template
def interpolate_html_safe(template: Template) -> str:
segments: list[str] = []
for item in template:
if isinstance(item, str):
segments.append(item)
else: # Interpolation value.
html_fragment = item.value
segments.append(html_fragment.replace("<", "<").replace(">", ">"))
return "".join(segments)
to_format = "<script>alert('Malicious JS');</script>"
html_page = t"<html>{to_format}</html>"
print(interpolate_html_safe(html_page))
# <html><script>alert('Malicious JS');</script></html>
# ^^^^ ^^^^ ^^^^ ^^^^
BREAKING: Guido van Rossum Returns as Python's BDFL on 15-05-2025 10:05
In this satirical YouTube video, Guido van Rossum announces he's returning as the BDFL, that to write Pythonic code you need to be one with whitespace, and that Python is getting a new keyword: maybe
.
If you're into Python, this short video is a hoot!
GitHub - treyhunner/countdown-cli: Full-screen countdown timer, centered in the terminal window by Trey Hunner on 15-05-2025 08:01
This Python package by Trey Hunner can be used from the command line to display a basic countdown timer in the terminal window.
Installing it with uv tool install countdown-cli
, I got the countdown
command that I can run with countdown 50s
, or countdown 5m
, or countdown 3m30s
:
Static Badge | Shields.io on 14-05-2025 22:05
The shields.io website is the provider of all those badges you see online, typically on GitHub README files and profiles:
To generate these, you can go to https://shields.io/badges to build the URL with the query parameters to define the badge you want. The samples above don't even scratch the surface of what you can do with these badges!
PEPs & Co. (via) by Hugo van Kemenade on 14-05-2025 12:24
In this short article, Hugo recounts Barry Warsaw's story about how PEPs (Python Enhancement Proposals) came to existance, and apparently โPEPโ is a backronym: first, Barry came up with the acronym โPEPโ and then it figured PEP could stand for Python Enhancement Proposal.
Regex affordances by Ned Batchelder on 09-05-2025 19:00
This short but instructional post by Ned covers some useful and not-so-common regular expression features, namely:
(?P<group_name>)
;(?x)
; andre.sub
.The regex with dynamic replacement was being used to replace references to $
-prefixed variables.
In the post, Ned wrote a function substitute_variables(text: str, variables: Mapping[str, str]) -> str
that you could use like this:
print(
substitute_variables(
"Hey, my name is $name!",
{"name": "Rodrigo"},
)
) # Hey, my name is Rodrigo!
Ned also shared a link to the function in its real context in coverage.py
.
pre-commit: install with uv (via) by Adam Johnson on 07-05-2025 14:25
In this post, Adam shares how he uses uv to install pre-commit using uv's tool
command:
> uv tool install pre-commit
What Adam adds, and that I didn't know about, is the plugin pre-commit-uv
, which makes pre-commit use uv to manage its Python-related hooks, speeding up the pre-commit checks.
After reading this article, I reinstalled pre-commit:
> uv tool install pre-commit --with pre-commit-uv
Perlisisms - "Epigrams in Programming" by Alan J. Perlis on 06-05-2025 13:34
I've read Alan J. Perlis's epigrams a dozen of times. Some of them I don't understand. Others, highly resonate with me.
One of my favourite epigrams must be number 10 in the linked article, and in particular, the second half:
โ[...] The only difference(!) between Shakespeare and you was the size of his idiom list - not the size of his vocabulary.โ
I really like this idea because if you think about Python, for example, there is a finite and fairly small number of built-in functions. However, the ways in which you can combine them are orders of magnitude larger, and the more you try to combine the built-ins, the syntactic features of the language, and the modules, the more expressive your code becomes.
That is how I feel about my favourite line of code:
sum(predicate(value) for value in iterable)
A fairly simple line of code that combines the built-in sum
, duck typing, and a generator expression.
When you put the three together, you get an idiom that counts how many elements of the given iterable
satisfy the given predicate.
Orbifolds and Other Games - A Local LRU Cache by Moshe Zadka on 05-05-2025 14:43
In reply to one of the Python tips I sent to my Python drops ๐๐ง newsletter, a reader sent me this article explaining how functools.lru_cache
might create issues in a threaded program.
After all, adding a cache around a function introduces global state that's shared across threads and that isn't properly protected against that.
The article explores an example and explains the many ways in which things can go wrong, and then suggest a better way to use the cache that makes it much safer and that is a technique that I appreciated.
Instead of adding the cache as a decorator where the function is defined, the cache can be added on the call site by using the decorator explicitly as a callable:
cached_func = lru_cache(maxsize=1024)(my_function)
By doing this, it is clear that the cache will live for as long as the variable cached_func
lives, and that the scope will be limited to the context in which cached_func
is defined, which will be narrower than the module-level scope of the original function.
For a more detailed exploration of the problems that might be introduced by a global cache, read the original article!
14 Advanced Python Features by Edward Li on 29-04-2025 14:45
In this article, the author covers 14 Python features and briefly shows how to use them. It's arguable whether the 14 features presented really are โadvancedโ, but the features presented are definitely useful.
The highlights for me were the features related to typing (overloads, protocols, and generics) and the fact that my own blog was used as a reference for a subsection on comparison operator chaining!
Solving a "Layton Puzzle" with Prolog by Hillel Wayne on 25-04-2025 14:47
In this article, the author solves a logic puzzle with Prolog. The puzzle is as follows:
Mary, Dan, Lisa, and Colin took a test with 10 True/False questions. Here are their answers and scores:
Name | Answers | Score |
---|---|---|
Mary | FFTFTFFTFF |
7/10 |
Dan | FTTTFTFTTT |
5/10 |
Lisa | FTTTFFFTFT |
3/10 |
Colin | FFTTTFFTTT |
?/10 |
Your objective is to find out Colin's score.
The author proceeded to write some Prolog code to solve this.
I don't know Prolog but I was quite fascinated with some of the ideas presented (I can't judge the brilliance of the ideas because of my ignorance; maybe these are standard in the Prolog world).
For example, here's a recursive definition for score
which I'll attempt to interpret:
% The student's test score
% score(student answers, answer key, score)
score([], [], 0).
score([A|As], [A|Ks], N) :-
N #= M + 1, score(As, Ks, M).
score([A|As], [K|Ks], N) :-
dif(A, K), score(As, Ks, N).
I think the three terms score(...)
set up some sort of predicates that Prolog can evaluate to true or false, and the first one is the base case: for an empty answer sheet and for a test with no questions, the score is 0.
Then, this line:
score([A|As], [A|Ks], N) :-
N #= M + 1, score(As, Ks, M).
Earlier in the article, the author says that you can write โA #= B + 1
to say "A is 1 more than B"โ.
And the code [A|As], [A|Ks]
seems to do some kind of pattern matching on the first element of both lists when they are the same.
So, this seems to be saying that score([A|As], [K|Ks], N)
will be true if:
N
is 1 more than M
; andscore(As, Ks, M)
is true.Similarly,
score([A|As], [K|Ks], N) :-
dif(A, K), score(As, Ks, N).
seems to be saying that the predicate score([A|As], [K|Ks], N)
will be true if:
A
is different from B
; andscore(As, Ks, N)
is true.โWriting for Developers: Blogs that get readโ book on Goodreads on 25-04-2025 14:30
I read the book โWriting for Developersโ as part of Phil Eaton's book club. I liked the tips that the authors give on how to work a draft and how to share your work once it's published, but I don't think this needed to be a 400-page book. It was way too long and I expected it to contain more advice on the act of writing itself.
cpython/Lib/collections/__init__.py module-level __getattr__ (via) on 21-04-2025 20:06
Raymond Hettinger talked about the module-level dunder method __getattr__
and then linked to an old version of the module-level attribute of the module collections
.
This module-level __getattr__
is used to issue deprecation warnings when certain aliases from the module collections.abc
are accessed.
It wouldn't make sense to issue the deprecation warnings as soon as the module is imported, or when a different object is imported from the module, so the module-level __getattr__
is put in place for that effect:
def __getattr__(name):
# For backwards compatibility, continue to make the collections ABCs
# through Python 3.6 available through the collections module.
# Note, no new collections ABCs were added in Python 3.7
if name in _collections_abc.__all__:
obj = getattr(_collections_abc, name)
import warnings
warnings.warn("Using or importing the ABCs from 'collections' instead "
"of from 'collections.abc' is deprecated since Python 3.3, "
"and in 3.10 it will stop working",
DeprecationWarning, stacklevel=2)
globals()[name] = obj
return obj
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
cpython/Lib/typing.py module-level __getattr__ (via) on 21-04-2025 19:56
Raymond Hettinger talked about module-level __getattr__
and __dir__
on Twitter/X and someone commented with a link to the module typing
that defines a module-level __getattr__
as an excellent example of the usage of that dunder method to implement lazy loading of some names.
In the case of the module typing
, the dunder method __getattr__
is used to lazily import some deprecated objects that the module typing
exposes and that are expensive to create.
Since they're rarely needed, deferring their creation to a module-level __getattr__
makes importing the module typing
much faster.
What I find particularly instructive about this example is that it shows how the module-level __getattr__
should behave:
AttributeError
if that name isn't part of the module;globals()[attr] = obj
; anddef __getattr__(attr):
"""Improve the import time of the typing module.
Soft-deprecated objects which are costly to create
are only created on-demand here.
"""
if attr == "ForwardRef":
obj = _lazy_annotationlib.ForwardRef
elif attr in {"Pattern", "Match"}:
import re
obj = _alias(getattr(re, attr), 1)
elif attr in {"ContextManager", "AsyncContextManager"}:
import contextlib
obj = _alias(getattr(contextlib, f"Abstract{attr}"), 2, name=attr, defaults=(bool | None,))
elif attr == "_collect_parameters":
import warnings
depr_message = (
"The private _collect_parameters function is deprecated and will be"
" removed in a future version of Python. Any use of private functions"
" is discouraged and may break in the future."
)
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
obj = _collect_type_parameters
else:
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
globals()[attr] = obj
return obj
Factorio Learning Environment (via) by Jack Hopkins, Mart Bakler, and Akbir Khan on 18-04-2025 13:50
This short paper introduces an LLM leaderboard based on the simulation/automation game Factorio. The authors created a programmatic interface to the game and then several LLMs were asked to play a simplified version of the game through that programmatic interface.
The LLMs were evaluated in two settings:
The paper shows that the models tested had very imbalanced scores, with Claude Sonnet 3.5 beating GPT-4o, Deepseek-v3, Gemini-2, Llama-3.3-7.0B, and GPT-4o-Mini, in both open play and lab play.
tariff ยท PyPI (via) on 15-04-2025 15:47
The parody Python package tariff
can be used to impose import tariffs on other modules!
The example from the package README:
import tariff
# Set your tariff rates (package_name: percentage)
tariff.set({
"numpy": 50, # 50% tariff on numpy
"pandas": 200, # 200% tariff on pandas
"requests": 150 # 150% tariff on requests
})
# Now when you import these packages, they'll be TARIFFED!
import numpy # This will be 50% slower
import pandas # This will be 200% slower
I don't know exactly how this is done, but I would guess it's enough to:
tariff.set
to set some sort of hook or โwatcherโ on the import system; and thenFactris โ Mathigon on 10-04-2025 22:25
This browser game is a rendition of Tetris with a very interesting mechanic. All blocks are rectangular and instead of rotating them, you can rearrange their widths and lengths while keeping their area the same. As an example, sometimes a 15x1 block will start falling and you can rearrange it as a 5x3, 3x5, or 1x15, block.
Elliptical Python Programming by Susam Pal on 10-04-2025 18:09
In this satirical short article, the author shows how to create Python programs using the built-in exec
and the snippet of code --(...==...)
, which evaluates to 1
:
print(--(...==...)) # 1
The comparison ...==...
evaluates to True
and the double negation converts to the integer 1
by going from True
to -1
, and then to 1
.
To save one character, the author could have opted for +(...==...)
, which also gives 1
.
By combining multiple together, the author produces larger integers:
print(
--(...==...)--(...==...)--(...==...)
) # 3
The author also points out that the leading --
can be ommitted:
print(
(...==...)--(...==...)--(...==...)
) # 3
Finally, by using exec
and string formatting with the format specifier %c
, the author can write arbitrary strings which can then be executed.
The author presents this code:
exec('%c'*((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))%(((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--(...==...),*(((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)),)*((...==...)--(...==...)),((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...))**((...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...)),((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)),((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))--(...==...)))
The beginning sets up the string that will represent the code to be executed:
exec(
'%c'
* ((...==...)--(...==...)--(...==...))
* ((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))
% (...)
)
This is equivalent to
exec(
'%c'
* 3
* 7
% (...)
)
The part '%c' * 3 * 7
creates a string with length 21: '%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c'
.
The part % (...)
creates a tuple with 21 integers that will be formatted onto that string.
For example, the character "a"
corresponds to the Unicode code point 97, so "%c" % 97
gives "a"
:
>>> ord("a")
97
>>> "%c" % 97
'a'
This means the author wants to run a Python program that is 21 characters in length.
The 21 integers that fill up the source code are all presented linearly inside the given tuple, except for a small section of the code that looks like this:
exec(
'%c'
* ((...==...)--(...==...)--(...==...))
* ((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))
% (
...,
...,
...,
...,
...,
...,
...,
...,
...,
,*(
((...==...)--(...==...)--(...==...)--(...==...))
* ((...==...)--(...==...)--(...==...))
** ((...==...)--(...==...)--(...==...)),
)
* ((...==...)--(...==...)),
...,
...,
...,
...,
...,
...,
...,
...,
...,
...
)
)
That small section unpacks another tuple inside the main tuple, and the inner tuple is a 1-element tuple that is multiplied by 2 ((...==...)--(...==...)
) to create a 2-element tuple with two equal values.
Here are the 21 integers the author defines, counting twice the one that is duplicated inside an inner tuple:
Char | Ord | Translation | Code |
---|---|---|---|
p |
112 | 4 * 4 * 7 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)) |
r |
114 | 4 * 4 * 7 + 2 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)) |
i |
105 | 3 * 5 * 7 |
((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)) |
n |
110 | 3 ** 3 * 4 + 2 |
((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)) |
t |
116 | 4 ** 2 * 7 + 4 |
((...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)) |
( |
40 | 2 * 4 * 5 |
((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)) |
' |
39 | 6 ** 2 + 3 |
((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)) |
h |
104 | 4 * 5 ** 2 + 4 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)) |
e |
101 | 4 * 5 ** 2 + 1 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--(...==...) |
l |
108 | 4 * 3 ** 3 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)) |
l |
108 | 4 * 3 ** 3 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)) |
o |
111 | 3 ** 3 * 4 + 3 |
((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)) |
, |
44 | 6 * 7 + 2 |
((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)) |
|
32 | 2 ** 5 |
((...==...)--(...==...))**((...==...)--(...==...)--(...==...)--(...==...)--(...==...)) |
w |
119 | 4 ** 2 * 7 + 7 |
((...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)) |
o |
111 | 3 ** 3 * 4 + 3 |
((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)--(...==...)) |
r |
114 | 4 * 4 * 7 + 2 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))--((...==...)--(...==...)) |
l |
108 | 4 * 3 ** 3 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...))**((...==...)--(...==...)--(...==...)) |
d |
100 | 4 * 5 ** 2 |
((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...)) |
' |
39 | 6 ** 2 + 3 |
((...==...)--(...==...)--(...==...)--(...==...)--(...==...)--(...==...))**((...==...)--(...==...))--((...==...)--(...==...)--(...==...)) |
) |
41 | 2 * 4 * 5 + 1 |
((...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...))*((...==...)--(...==...)--(...==...)--(...==...)--(...==...))--(...==...) |
Ray Tracing in One Weekend by Peter Shirley, Trevor David Black, Steve Hollasch on 07-04-2025 13:12
This short book walks you through building a ray tracer โin one weekendโ. I haven't gone through this book yet but I've been toying with the idea of writing a ray tracer so I am really excited about giving this a go.
Opening statements - PyCon 2014 - YouTube on 26-03-2025 11:33
Brett Cannon attributes himself the phrase โI came for the language, but stayed for the communityโ that he says around the 16:46 minute mark of the linked video.
I liked finding out about this because I always say this phrase to folks, so it's neat knowing where it comes from.
GitHub - sharkdp/pastel: A command-line tool to generate, analyze, convert and manipulate colors on 20-03-2025 00:46
The command line tool pastel
appears to be a brilliant CLI to work with colours in the command line.
For some reason, I find myself wanting to mix colours together quite frequently, for example, and I can do this with pastel:
pastel mix f8f8f2 darkblue | pastel format rgb
# rgb(148, 122, 192)
The reason I'm piping pastel mix
into pastel format
is because the output of pastel mix
is actually more complete:
Animation vs. Coding - YouTube by Alan Becker on 19-03-2025 20:50
In this brilliant video, Alan Becker shows us a world where a stick figure fights a computer through programming. I can't do a great job at explaining what's happening other than the fact that the two characters are fighting and writing Python code to use as weapons and to manipulate the arena...
What I enjoyed the most was the attention to detail and the fact that we were shown the code and the code matched the animations quite faithfully. For example, at one point, the computer creates a โweaponโ that โfiresโ letters from the alphabet:
import string as ammo
gun = list(ammo.ascii_uppercase)
print(gun.pop())
As the animation progresses and gun.pop()
keeps getting called, you see the alphabet being fired back to front, and once the letter A is fired, the gun is empty and you hear the click of a gun without ammo.
Just wonderful!
Sierra Leone is the worldโs roundest country (and Egypt the squarest one) - Big Think on 14-03-2025 12:32
Apparently, two different people decided to study which countries were the most rectangular and which were the most circular. (One person per shape, not both working together in the two rankings.)
As far as I understood, a country is as rectangular as the proportion of its area that overlaps with a rectangle with the same area as the country. The most rectangular country is Egypt, with a score of 0.955. The Vatican City comes in in second place, with a score of 0.948. Funnily enough, the Vatican City is also the fourth roundest country, with a score of 0.908. The roundest country is Sierra Leone, with a score of 0.934.
Portugal is the 55th most rectangular country, with a score of 0.878. The least rectangular country is the archipaelago of the Maldives, with a score of 0.018. Maldives's score is easy to understand: there are too many islands and they're all relatively small โ as opposed to Portugal, for example, that does have some islands but they're much smaller when compared to the mainland.
How Core Git Developers Configure Git by Scott Chacon on 03-03-2025 13:47
In this article, Scott goes over some git settings that many git core developers have set in their own git configuration files. I won't go over all the settings that Scott suggests we set, but I will share some of the settings that I have adopted:
# ~/.gitconfig
[diff]
algorithm = histogram
colorMoved = plain
mnemonicPrefix = true
renames = true
[push]
default = simple
autoSetupRemote = true
followTags = true
[fetch]
prune = true
pruneTags = true
all = true
And out of these settings, the ones I find the most useful are the ones under diff
.
histogram
is a slightly slower diff algorithm that tends to yield better results when things are moved around, which I do a lot; setting colorMoved = plain
will also use colours to represent things that were moved around, instead of showing up in red where they were moved from and green where they were moved to; mnemonicPrefix
will use the letters i
, w
, and c
, in diffs, to indicate where the diff is coming from (index, working directory, or commit, respectively); and renames
will let you know if a file was renamed.
I recommend you go through the article linked to learn about some other settings and to adopt the ones you like.
Slashing my '.bashrc' in half โ Bite code! on 27-02-2025 15:58
In this article, the author shares some tools they use and that have allowed them to greatly simplify their .bashrc
configuration file.
For me, the main thing(s) I took from the article are the tools shared, and that I am going to try out.
I started using Atuin, a shell history handler, a couple of weeks ago based on a recommendation from this same author; now, the article mentions starship (which I also use already), ripgrep (an alternative to grep
), fd (an alternative to find
), and fzf
, a universal fuzzy finder that I'm installing as I type these words.
Do-nothing scripting: the key to gradual automation (via) by Dan Slimmon on 17-02-2025 19:48
In this article, Dan defines โdo-nothing scriptingโ and presents it as an excellent starting point to making it easier to automate boring, repetitive tasks that don't really add long-term value to your project or life. The idea is simple: create a basic script that essentially just prompts the user to do each step of the boring repetitive task, and wait for user confirmation before showing the steps for the next task.
Here's an example for the task of creating a testimonial on my website based on what someone writes on social media:
do-nothing-testimonial.py
def wait():
input("Press Enter when done...")
print("Copy an existing testimonial as the boilerplate.")
wait()
print("Delete details of the copied testimonial.")
wait()
print("Copy the text of the online testimonial into the new page.")
wait()
print("Fill in details of the online testimonial.")
wait()
print("Does the review have an associated profile with a picture?")
has_pic = input("y/n >>> ").strip().casefold()
if has_pic.startswith("y"):
print("Download the picture.")
wait()
print("Compress it with optimizt and put it in the page folder.")
wait()
else:
print("Generate a new picture.")
wait()
print("Add & commit the new testimonial.")
This script is already valuable because it gives me all of the steps I need to follow and makes sure I don't forget anything.
Furthermore โ and I feel like this is the greatest advantage โ the fact that the script already exists means I can gradually replace sections with their automated versions instead of just printing instructions to follow.