TGViewer
Находки в опенсорсе Находки в опенсорсе @opensource_findings · 12.6K subscribers
Post #843 5.56K
История одного PR #prhistory

Некоторое время назад я добавил в mypy поддержку ReadOnly special form для TypedDict: https://github.com/python/mypy/pull/17644
PR большой, его будут смотреть еще некоторое время. Но, о самых важных принципах рассказать можно уже сейчас.

1. Что такое ReadOnly?

PEP: https://peps.python.org/pep-0705

По сути, он запрещает такой код:


from typing import ReadOnly, TypedDict

class User(TypedDict):
username: ReadOnly[str] # you cannot change the value of `user['username']`

user: User = {'username': 'sobolevn'}
user['username'] = 'changed' # type error! username is read-only


Крайне полезная вещь для бизнес логики.

2. ReadOnly был добавлен в Python в версию 3.13 мной некоторое время назад: https://github.com/python/cpython/pull/116350

Однако, он был добавлен в typing_extensions еще раньше: https://github.com/python/typing_extensions/commit/0b0166d649cebcb48e7e208ae5da36cfab5965fe
Так что пользоваться typing_extensions.ReadOnly можно будет как только выйдет новая версия mypy с поддержкой данной special form.

3. Как устроен ReadOnly?

Основная сложность, что разные special form'ы могут идти вместе:
- username: ReadOnly[Required[str]]
- age: NotRequired[Annotated[ReadOnly[int], Validate(min=18, max=120)]]
- и тд в любых комбинациях

Внутри TypedDict появились специальные атрибуты: __readonly_keys__ и __mutable_keys__:


>>> from typing import TypedDict, ReadOnly
>>> class User(TypedDict):
... username: ReadOnly[str]
... age: int
...
>>> User.__readonly_keys__
frozenset({'username'})
>>> User.__mutable_keys__
frozenset({'age'})


4. Какие делатали типизации важны?

Помимо очевидного запрета на изменение ReadOnly ключей, нужно помнить, про отношение подтипов.

Пример:


User = TypedDict('User', {'username': ReadOnly[str]})
MutableUser = TypedDict('MutableUser', {'username': str})

def accepts_user(user: User): ...
def accepts_mutable_user(user: MutableUser): ...

ro_user: User
mut_user: MutableUser

# MutableUser является подвидом User, но User не является подвидом MutableUser
accepts_user(mut_user) # ok
accepts_mutable_user(ro_user) # error: expected: MutableUser, given: User


Но почему?
Потому что тело функции accepts_mutable_user может выглядеть как-то так:


def accepts_mutable_user(user: MutableUser):
user['username'] = ''


Таким образом – мы могли бы допустить ошибку и изменить "неизменяемый" ключ.

Ждём? 🤔
GitHub Add `ReadOnly` support for TypedDicts by sobolevn · Pull Request #17644 · python/mypy Refs #17264 I will add docs in a separate PR.
  • 🔥 49
  • 👍 14
  • 🤔 3
  • 👎 1
  • 😱 1
More from @opensource_findings
  1. Sep 28, 2026Еще анонсы докладов на бесплатную конференцию 17 октября в НН Регистрация: https://itgorky…
  2. Sep 25, 2026Монадические выражения в Python. Наконец-то! Продолжаем разговор про интересные PEPы. Пого…
  3. Sep 15, 2026PEPы в Python окончательно вышли из-под контроля Давайте посмотрим, что происходит с ПЕПам…
  4. Sep 10, 2026Большая бесплатная конференция в Нижнем Новгороде 17 октября Регистрация: https://itgorky.…
  5. Sep 1, 2026Post #985
  6. Aug 24, 2026JIT могут удалить из CPython! PEP-836: https://peps.python.org/pep-0836 JIT в CPython имее…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →