Sign in

Rodrigo Girão Serrão 🐍🚀

@mathspp.com
1.2K followers 313 following 1.3K posts

I'll help you take your Python skills to the next level! Python deep dive every Monday 🐍🚀 -> mathspp.com/insider Short daily drop of Python knowledge 🐍💧 -> mathspp.com/drops

PostsRepliesMedia
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 24/04/2026
Your frontier of competence is expanded by LLMs based on your current knowledge.
A diagram showing how LLMs are more useful if you know more a priori. Two circles of different sizes represent your current knowledge and the LLM extends either circle by the same amount, so if your initial circle is larger than your ending circle is also larger.
210
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 17/03/2026
Too many arrows?
Diagram using recursive structural pattern matching and unpacking inside comprehensions (new in Python 3.15) to write a function that flattens deeply nested lists of objects.
140
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 12/03/2026
Python decorators blueprint: a step by step explanation of the full decorator anatomy. Steal this for when you're writing your own decorators.
Diagram showing the full decorator anatomy and the 9 parts that make up a general decorator.
172
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 19/02/2026
`itertools.compress` explained in a simple diagram. Useful when you need to filter an iterable of data based on a second iterable of flags or selectors.
Diagram showing how `itertools.compress` works, taking an iterable with data and an iterable with selectors and making a selection based on the Truthy/Falsy value of the selectors.
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 18/02/2026
How does this slide look?
Diagram explaining how starmap works across a stream of data for a given iterable with pairs (a, b) and a binary function f, producing the iterable with items f(a0, b0), f(a1, b1), etc.
000
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 03/02/2026
If they had used the built-in `range` this wouldn’t happen.
Photo of a set of buttons inside an elevator. From the bottom up, the numbers go 2, 0, 4, 5, 6, …
250
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 02/02/2026
It's pretty cool how much you can get done in Python with so little code if you leverage the standard library.
Python code snippet with the following code:

from collections import deque
from itertools import islice, tee

def nwise(iterable, n):
    iterators = tee(iterable, n)
    for offset, it in enumerate(iterators):
        deque(islice(it, offset), maxlen=0)
    yield from zip(*iterators)
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 30/01/2026
One of my students today made this mistake. If you have a project and want to test it, your tests will import your project. So, you need to install pytest as a dependency INSIDE your project. If you install it globally, when you run it, it can't see your own project.
Diagram showing how using uv to install pytest globally will isolate it from everything else, as it should. But if you're testing a package, you want to have pytest as a dependency of that package since it'll be able to find your own package code.
200
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 30/01/2026
Dump JSON and make it Human-readable.
Diagram showing how using the parameters `indent` and `sort_keys` can make the output of dumping JSON much better in Python.
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 29/01/2026
Parse JSON from a string with a single function call. Don't use this if you have JSON in a file. Use `load` for that. This is for when the data comes in a payload, for example.
Simple diagram showing how to use `json.loads` to parse a native Python object from a JSON string.
000
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 28/01/2026
When writing CLIs and scripts for the terminal, use `sys.exit` to set the exit code of your program. ✅ An exit code of 0 means success; your program terminated without any issues. ❌ Any other integer exit code means “error”.
Diagram showing how to use `sys.exit` to set the exit code of a program and how to use the command `echo $?` to check the exit code of the last command in the terminal.
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 27/01/2026
Before being added in Python 3.10, itertools.pairwise was commonly implemented in terms of itertools.tee. Isn't this elegant?
Diagram showing how to implement pairwise in terms of tee.

Code:
def my_pairwise(it):
    it1, it2 = tee(it, 2)
    next(it2)
    yield from zip(it1, it2)
121
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 26/01/2026
`typing.reveal_type` explained: Runtime vs static type checking time.
The module `typing` provides a debugging aid, the function `reveal_type`.
When a static type checker encounters it, the static type checker emits a note with the _inferred_ type of the expression passed into `reveal_type`:

```py
def s() -> str:
    return 3

reveal_type(s())
# Revealed type is "builtins.str"
```

The function `reveal_type` doesn't even need to be imported and the static type checker infers that `s()` is a string because of the return type `-> str`.
(The static type checker also complains about the fact that we're returning an integer from inside `s` while the function should return a string, but you'll see why I'm doing that in a second.)

At runtime, the function `reveal_type` prints the runtime type of the argument to `sys.stderr` and returns the argument unchanged:

```py
from typing import reveal_type

def s() -> str:
    return 3

reveal_type(s())
# Runtime type is 'int'
```

At runtime, the type of `s()` is `int` because the function `s` returns the integer 3.
000
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 25/01/2026
Python typing pop quiz. Take a look at the function `choice` shown below. Note how it's being called as `choice(3, "hey!")`. Will this type check or not? If it does, what's the revealed type? The right answer might surprise you! It surprised me. 🤷
Code listing with the following Python code:

from typing import reveal_type
import random

def choice[T](left: T, right: T) -> T:
  if random.random() < 0.5:
    return left
  else:
    return right

reveal_type(choice(3, "hey!"))
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 23/01/2026
Python has a flexible mechanism built into `pathlib` to look for files with filenames that follow a given pattern. You can use `[...-...]` to represent character ranges and `*` to match arbitrary text. But note that `glob` does NOT support regex syntax! This is just similar.
Diagram showing how character ranges and the asterisk can be used in `pathlib.Path.glob` to match multiple filenames that follow the same pattern.
121
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 21/01/2026
These two typing features look similar... But they're very different. Here's how to use NewType and TypeAlias in Python 👇
Diagram with code snippets showing how to use TypeAlias and NewType. The content of the post walks through the diagram.
111
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 21/01/2026
Pandas 3.0.0 is out now!
Screenshot of the pandas 3.0.0 release on GitHub.
011
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 21/01/2026
Just finished scheduling 3 tips for today, tomorrow and Friday. If you want to get smarter about Python in just 3 min/day, follow the QR code to sign-up.
Screenshot of my newsletter scheduled posts showing three upcoming emails: typealias vs newtype, character ranges in glob search, and expanding regex matches. The image also includes a QR code that takes you to mathspp.com/drops where you can sign-up to get these tips.
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 20/01/2026
Convert data to a JSON string without writing to a file: Just use `json.dumps`! (The final S stands for String!) ```py import json data = {"k1": True, "k2": [73, 42, 10]} s = json.dumps(data) print(type(s), s) # <class 'str'> {"k1": true, "k2": [73, 42, 10]} ```
Diagram showing how to use `json.dumps` to convert data into a JSON string.

Code snippet:

```py
import json

data = {"key1": True, "key2": [73, 42, 10]}

dumped = json.dumps(data)
print(type(dumped), dumped)
# str {"key1": true, "key2": [73, 42, 10]}
```
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 19/01/2026
Let me teach you to break a Python law. Iterators can only be traversed once: ```py squares = (x ** 2 for x in range(3)) for sq in squares: print(sq, end=", ") # 0, 1, 4, for sq in squares: print(sq, end=", ") # <no output> ``` The second loop produces no output!
Diagram showing how to use `itertools.tee` to split an iterator. The code snippets of the diagram are the ones in the post.
140
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 18/01/2026
`itertools.pairwise` takes an iterable and produces overlapping pairs of consecutive elements: ```py from itertools import pairwise queue = ["John", "Joe", "Ana"] for f, b in pairwise(queue): print(f"{f}'s ahead of {b}.") # John's ahead of Joe. # Joe's ahead of Ana. ```
Diagram showing how `itertools.pairwise` works.

Code snippet:

```py
from itertools import pairwise

queue = ["Harry", "Hermione", "Ron"]

for front, back in pairwise(queue):
    print(f"{front} is directly in front of {back}.")
```

Output:

```text
Harry is directly in front of Hermione.
Hermione is directly in front of Ron.
```
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 17/01/2026
This uv cheatsheet lists 40+ of the most and useful uv commands. It groups the commands into 9 sections that correspond to major features/capabilities that uv has. The cheatsheet is free to download and comes in light mode, dark mode, and high-contrast mode. mathspp.gumroad.com/l/cheatsheet...
uv cheatsheet with 40+ commands and variations split into 9 categories.
043
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 16/01/2026
A regex match has the method `groups`. This method returns a tuple with all of the groups that the pattern contained. Here's an example 👇
Diagram showing how to use `re.Match.groups` with a default value. The code snippets in the diagram match the ones shared in the post(s).
200
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 14/01/2026
When a method returns the argument `self`, use `typing.Self` to annotate the return type of the method: ```py from typing import Self class P: def some_method(self) -> Self: ... return self ``` Why is this useful?
Diagram showing how to use `typing.Self`.

Code snippet that doesn't use `Self` and has a problem:

```py
from typing import reveal_type

class P:
    def return_self(self) -> P:
        return self
        
class C(P):
    pass
    
c = C()
reveal_type(c.return_self())  # P
```

Code snippet that uses `Self` and is better:

```py
from typing import reveal_type, Self

class P:
    def return_self(self) -> Self:
        return self
        
class C(P):
    pass
    
c = C()
reveal_type(c.return_self())  # C
```
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 13/01/2026
Whenever you are using validator functions you run the risk of forgetting to call the validator. This means you'll be passing around data that might be invalid. You can prevent this from happening by writing a “parser” function instead of a validator. But what do I mean?
Diagram with code snippets summarising the technique.

First code snippet:

```py
from typing import NewType

Email = NewType("Email", str)

def parse_email(email: str) -> Email:
    # Check if it's valid...
    return Email(email)
    
def login(email: Email): ...
```

If you forget to “validate”, the type checker complains:

```py
email = input("Enter an email >> ")
login(email)  # Type error!
```

Fix it by using the “parser”:

```py
email = input("Enter an email >> ")
email = parse_email(email)
login(email)
```
100
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 12/01/2026
You can use the module `json` to read and write data in the JSON format, which is very suitable to represent the most common Python built-in types: - lists - dictionaries - strings - integers - floats - Booleans.
Diagram showing how to use the module `json`.

First snippet of code:

```py
import json

data = {
  "name": "Rodrigo",
  "newsletters": 2,
}

with open("data.json", "w") as f:
    json.dump(data, f)
```

Second snippet of code:

```py
import json

with open("data.json", "r") as f:
    data = json.load(f)
    
print(data)
# data = {
#   "name": "Rodrigo",
#   "newsletters": 2,
# }
```
200
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 11/12/2025
It's so ANNOYING to break out of nested loops. Auxiliary variables, conditional statements, non-linear paths through your code... It's a mess! But if you extract the looping logic to a generator, you get: - less indentation ✨ - a flat loop you can easily break out of ✨
Diagram showing the trick in action.

Code before simplification:

done = False
for n in range(1, len(switches) + 1):
    for group in combinations(switches, n):
        state = simplify(group)
        if state == target:
            print("found!")
            done = True
            break
    if done:
        break

Code after simplification:

def groups_of_switches(switches):
    for n in range(1, len(switches) + 1):
        for group in combinations(switches, n):
            yield group

for group in groups_of_switches(switches):
    state = simplify(group)
    if state == target:
        print("found!")
        break
1172
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 10/12/2025
📢 Public service announcement: Don't buy from me: Don't buy my books. Don't sign-up for my courses. Don't attend my cohorts. Apparently, I'm a scammer. 🤷 At least, that's my interpretation of this comment from a subscriber... Should I delete them from my mailing list?
Upsetting screenshot of a poll response that reads “Go get a real job. Stop trying to take money from hard-working people who earned it fair and square. This is algorithm r***! What’s being r***d are people’s wallets.”
230
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 08/12/2025
A visualisation of day 7 from Advent of Code. Worked on this during yesterday's stream, although the colouring was added off stream.
041
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 07/12/2025
7 days of Advent of Code diagrams from my streams and analysis sessions!
A collection of lots of random diagrams, scribbles, and coloured lines, but all of them too small to make out any relevant information.
030
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 04/12/2025
Anatomy of a list comprehension. Understand this and you'll never need to look up list comprehensions again.
Diagram showing the anatomy of a list comprehension: 1. data transformation; 2. data source; 3. optional data filter.

Code for general list comprehension:
my_list = [
    data_transformation(value)
    for value in data_source
    if predicate(value)
]

A concrete example:
my_list = [
    n ** 2
    for n in range(1000)
    if (n % 3 == 0) or (n % 5 == 0)
]
031
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 02/12/2025
Here's a lie developers tell themselves: “Recursion is useless” That's a coping mechanism because you're afraid of recursion... But you shouldn't be afraid! Recursion is actually nice ✨ And it's a very natural way to express certain algorithms... Let me show you an example.
Diagram showing how to use recursion when computing permutations.
Code:

def perm_(values):
    if not values:
        yield ()
        return

    for idx, value in enumerate(values):
        rest = values[:idx] + values[idx + 1:]
        for sub_perm in perm_(rest):
            yield (value,) + sub_perm
100
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 26/11/2025
In today's session of the Algorithm Mastery Bootcamp we talked about linear recurrence relations and how to represent them as matrices. The next step? Using fast exponentiation with repeated squaring to compute terms at LIGHTNING FAST speeds ⚡️🤓
Excalidraw diagram showing how the rows of a matrix of 0s and 1s can be seen as a series of “selectors” for the results of a multiplication with a column vector.
030
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 20/11/2025
Here's how to NOT get a file name from a path in Python. The string method `split` has a counterpart `rsplit` that starts splitting from the end of the string. This is useful if you only want the final segment(s) of a string.
Diagram showing how split and rsplit work. Code:

>>> "This is bananas".split()
['This', 'is', 'bananas']
>>> "This is bananas".rsplit()
['This', 'is', 'bananas']

s = "This is bananas"
first, rest = s.split(maxsplit=1)
print(first)  # This
print(rest)  # is bananas

s = "This is bananas"
rest, last = s.rsplit(maxsplit=1)
print(last)  # bananas
print(rest)  # This is
120
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 19/11/2025
Who can guess what I'm writing about?
210
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 19/11/2025
Do you work in Hollywood or at a publisher? If you do, I've got the perfect Python string method for you: `str.title` Changes the case of any string into title case. “star wars: the empire strikes back” becomes “Star Wars: The Empire Strikes Back”. Much more professional!
Diagram showing how the string method `title` works. Code snippets:

print("star wars: the empire strikes back".title())
# Star Wars: The Empire Strikes Back

print("CrAzY cAsInG".title())
# Crazy Casing

print("1word2words3words".title())
# 1Word2Words3Words
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 17/11/2025
Need a temp file in Python? Maybe you need to test a function that requires a file-like object. Or maybe you need a buffer because you're processing an amount of data so large that it doesn't fit into memory... In these situations, `tempfile.TemporaryFile` can help you out.
A diagram showing how to use `tempfile.TemporaryFile` as a context manager. Full code:

import tempfile

with tempfile.TemporaryFile() as f:
    f.write(b"Hello ")
    f.write(b"world!")
    
    f.seek(0)
    print(f.read().decode())  # Hello world!

# File has been deleted at this point.
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 14/11/2025
When I'm feeling fancy, I define some methods in a more “functional” way. I just have fun writing code like this for myself 🤪 👇 yay or nay?
Terse snippet of code defining a class stack. Full code:

```py
from functools import partial, Placeholder as _P

class stack(list):
    put = list.append
    peek = partial(list.__getitem__, _P, -1)
    __iter__ = list.__reversed__  # LIFO iteration
```
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 14/11/2025
When doing Advent of Code I always end up needing a stack. When I do, I create it by inheriting from `list`. This gives me almost everything I want for free!
Diagram showing how to use the built-in `list` as a base class to create a stack. Code:

class stack(list):
    def put(self, value):
        self.append(value)

    def peek(self):
        return self[-1]

s = stack()
s.put(1)
s.put(2)
s.put(3)

print(f"The stack has {len(s)} items.")
# The stack has 3 items.

print(3 in s)  # True
popped = s.pop()
print(3 in s)  # False

if s:
    print(f"{popped} was at the top.")
    print(f"Now it's {s.peek()}.")
# 3 was at the top.
# Now it's 2.
241
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 13/11/2025
You know what's a pain to implement in Python? A moving average function. Manually keeping track of all the values being considered for the window is a pain! Unless you use `collections.deque`. Its param `maxlen` makes the window automatically evict the values you don't need.
Snippet of code showing a moving average implementation in pure Python. Code:

```py
from collections import deque
from itertools import islice

def moving_average(values, n):
    source = iter(values)
    window = deque(islice(source, n - 1), maxlen=n)
    averages = []
    for value in source:
        window.append(value)
        averages.append(sum(window) / n)
    return averages

moving_average([10, 20, 30], 2)
# [15.0, 25.0]
```
2583
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 12/11/2025
I was going through the OVERWHELMINGLY POSITIVE reviews of my book “Pydon'ts” and I always giggle when I find this 1-star review. I mean, after 200 ⭐️⭐️⭐️⭐️⭐️ reviews, this anonymous user could think for a second and realise that the issue wasn't mine/the book's... 🤡
Screenshot of the reviews of my book Pydon'ts. 4.9/5 average rating over 223 ratings; 96% reviews are 5 stars and a single review of 1 star.Screenshot of my only 1-star review complaining that the EPUB will not download and the pdf is not available for download!
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 11/11/2025
Your future you will thank you for this: The keyword `assert` takes an expression to its right. If the expression evaluates to `True`, or Truthy, everything is fine. But if it doesn’t, you get an `AssertionError`. That's a pretty unhelpful/generic error... Here's the fix:
Diagram showing how using a string with the keyword `assert` allows users to provide a custom error message instead of the plain/vanilla “AssertionError” you'd get if no custom string was provided.
120
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 10/11/2025
All Python objects have a Truthy/Falsy value. This is the value of an object when used in a Boolean context. For example, when used in the condition of an `if` statement. Most objects are Truthy, with a few exceptions. For most types, the “nothing” or “empty” value is Falsy.
Diagram showing a lot of Falsy values, like the 0, empty sets, dictionaries, or tuples, and some Truthy values, like non-zero integers and non-empty strings.
120
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 07/11/2025
When building complex readable strings, consider building them out of smaller fragments. The function `oxford_comma`, shown below, demonstrates this technique. What's the point of the smaller fragments?
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 06/11/2025
Python has a very well-known naming convention: Names that start with a leading underscore are “private”. This means the outside world has no business using them. E.g., attributes and methods starting with `_` in a class mean they're for that class only.
Diagram showing a snippet of code and how different objects may have a name that starts with an underscore. Snippet of code: class Car:     def __init__(self, ...):         self._serial_number = ...      def _start_engine(self): ...      def drive(self):         self._start_engine()         ...  def _inspect_car(workshop, car):     ...
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 05/11/2025
The methods `strftime` and `strptime` can be used to convert dates/times into strings and vice-versa. Here's a mnemonic to help you: 👉 `strptime` has a “P” for “Parse date/time”, so string -> date 👉 `strftime` has an “F” for “Format date/time”, so date -> string
Diagram showing the methods strftime and strptime in action. Code: import datetime as dt  date = dt.date.strptime("2025-11-04", "%Y-%m-%d") print(date)  # datetime.date(2025, 11, 4)  print(date).strftime("%Y-%m-%d") # 2025-11-04
010
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 04/11/2025
The string method `str.split` has a parameter `maxsplit` that determines the maximum number of splits that will be performed. The returned list with the splits (and possibly the remainder of the string) has a maximum length of `maxsplit + 1`.
Diagram showing how str.split works with the parameter maxsplit. Snippet of code: >>> "a/b/c/d".split("/", 1) ['a', 'b/c/d']  >>> "a/b/c/d".split("/", 2) ['a', 'b', 'c/d']  >>> "a/b/c/d".split("/", 3) ['a', 'b', 'c', 'd']  >>> "a/b/c/d".split("/", 4) ['a', 'b', 'c', 'd']
041
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 03/11/2025
Hah, you beat me to it. But I'll do it either way, it's the logical follow-up to my uv cheat sheet.
uv cheat sheet with sections for creating projets, managing dependencies and the project lifecycle, working with scripts and different Python versions, and more.
150
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 31/10/2025
This is your friendly reminder that str.split accepts a parameter to determine the maximum number of splits. You get up to n splits and the remainder of the string.
>>> "a/b/c".split("/", 2)
['a', 'b', 'c']
>>> "a/b/c/d".split("/", 2)
['a', 'b', 'c/d']
>>> "a/b/c/d/e".split("/", 2)
['a', 'b', 'c/d/e']
110
Rodrigo Girão Serrão 🐍🚀 @mathspp.com · 28/10/2025
Live-coding during trainings + automatically syncing to git so everyone has access to what I'm typing = Very informative git history.
Vertical/tall screenshot of git history with a large number of commits, all with the message "Auto sync commit".
000