惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
博客园 - 【当耐特】
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
WordPress大学
WordPress大学
D
Docker
T
The Blog of Author Tim Ferriss
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
云风的 BLOG
云风的 BLOG
F
Fortinet All Blogs
罗磊的独立博客
小众软件
小众软件
A
About on SuperTechFans
MyScale Blog
MyScale Blog
D
DataBreaches.Net
The GitHub Blog
The GitHub Blog
C
Check Point Blog
L
LangChain Blog

alexwlchan’s notes

What is WS11 1DB? Blocking referrers with Caddy How to type a Spanish question mark (¿) on a Mac Why does t.Setenv panic after t.Parallel? Use Path.glob() and Path.rglob() for typed versions of glob.glob() Curious clocks and colourful eyes Track which templates are used by Jinja2 Archeologists distinguish between “sherds” and “shards” A single command to test all my changed Go packages Disable the new message animations in WhatsApp Finding high-churn folders that bother Backblaze Always-on SSH agent forwarding with my Git pushes Managing the caption of a photo with AppleScript (but not PhotoKit) Goodhart’s and Campbell’s Law are different Notes from The Cornishman No. 176 (Spring 2026) Notes from The Cornishman No. 176 (Spring 2026) GitUp can’t diff text files larger than 8MB Home Testing the width of a page on a mobile device using Playwright Disable AirPods charging notifications Start a Caddy server in a subprocess during a Python session Filter a list of JSON object based on a list of tags HOME_GET_ME_HOME is a Citymapper Shortcuts action The FileExistsError exception exposes a filename attribute The red-lined bubble snail Why can’t Python connect to example.com? Useful type hints for Python How to truncate the middle of long command output AirPlay Receiver can interfere with Flask apps What’s the main prefix in SQLite queries?
Non-overlapping type comparisons and Python type checkers
2026-08-28 · via alexwlchan’s notes

Why do type checkers allow you to check if a str is a member of a list of int?

I made a mistake in Python today where I checked if a collection of one type contained an element of a different type. Here’s a minimal example:

numbers = [1, 2, 3, 4, 5]

print("one" in numbers)  # False

This is trivially false, right? A list of integers can’t contain a string, and I was surprised my type checker (ty) didn’t warn me. Then I did some reading, and I realised this isn’t quite as trivial as I thought.

When you use the in keyword, you go through several magic methods :

  • For item in collection, Python calls __contains__(self, item) on the collection.
  • If an object doesn’t define __contains__ but does define __iter__, Python iterates through the collection and looks for an element x where x is item or x == item is True.
  • When you call x == y, Python calls __eq__(self, y) on x.

The types int and str include their subclasses, which can override these magic methods. Usually they do the “obvious” thing and cross-type comparisons will report False – but it’s theoretically possible.

Cross-type comparisons are usually a mistake, and some type checkers will flag it, but not by default – mypy, Pylance and Pyright only flag it in strict mode, and it’s not yet supported in ty. It’s possible to write Python where this is the correct and desired behaviour, however confusing it might appear.

Counterexamples

I wrote a couple of simple programs where I check if a str is in a list[int] and the membership test returns True. I wouldn’t write anything this confusing in a real codebase, but I found it helpful to understand these magic methods.

Here’s a custom collection that overrides __contains__:

WORD_MAP = {"one": 1, "two": 2, "three": 3, "four": 4, "five": 5}


class NumberList(list):
    def __contains__(self, item: object) -> bool:
        # Check if `item` is a number (for example, `1 in numbers`)
        if isinstance(item, int) and super().__contains__(item):
            return True

        # Check if `item` is a string (for example, `"one" in numbers`)
        if isinstance(item, str) and item in WORD_MAP:
            return super().__contains__(WORD_MAP[item])

        return False


numbers: list[int] = NumberList([1, 2, 3, 4, 5])

print(1 in numbers)      # True
print("one" in numbers)  # True

print(6 in numbers)      # False
print("six" in numbers)  # False

Here’s another approach, where I subclass int and override the __eq__ method:

NUMBER_MAP = {1: "one", 2: "two", 3: "three", 4: "four", 5: "five"}


class Number(int):
    def __eq__(self, other: object) -> bool:
        # Check if `other` is a number (for example, x == 1)
        if isinstance(other, int) and super().__eq__(other):
            return True

        # Check if `other` is a string (for example, x == "one")
        for numeral, word in NUMBER_MAP.items():
            if super().__eq__(numeral) and other == word:
                return True

        return False


numbers = [Number(1), Number(2), Number(3), Number(4), Number(5)]

print(1 in numbers)      # True
print("one" in numbers)  # True

print(6 in numbers)      # False
print("six" in numbers)  # False