Forwarded from Никита Соболев
всем привет! я очень долго обещал сделать бесплатный курс на ютюбе для всех желающих. и вот я, наконец, начал его делать! 🎉
встречайте: https://www.youtube.com/@sobolevn
уникальность формата в том, что рассматриваю одну узкую тему с трех уровней сложности: junior, middle, senior. так что, контент должен быть интересным для всех уровней Python разработчиков!
обратите внимание, что курс не для тех, кто идет учить питон с нуля. он для тех, кто уже хоть немного знает, как погромировать на питоне.
важные ссылки:
- все материалы курса: https://github.com/sobolevn/the-best-...
- мой гитхаб: https://github.com/sobolevn
- поддержать мою работу: https://boosty.to/sobolevn
- вступить в наше новое глобальное сообщество: https://discord.python.ru
буду рад обратной связи!
в ближайших планах:
- починить звук и свет
- избавиться от слова "интерсный" в описании примерно всего 😄
- сделать много новых видео по разным темам
встречайте: https://www.youtube.com/@sobolevn
уникальность формата в том, что рассматриваю одну узкую тему с трех уровней сложности: junior, middle, senior. так что, контент должен быть интересным для всех уровней Python разработчиков!
обратите внимание, что курс не для тех, кто идет учить питон с нуля. он для тех, кто уже хоть немного знает, как погромировать на питоне.
важные ссылки:
- все материалы курса: https://github.com/sobolevn/the-best-...
- мой гитхаб: https://github.com/sobolevn
- поддержать мою работу: https://boosty.to/sobolevn
- вступить в наше новое глобальное сообщество: https://discord.python.ru
буду рад обратной связи!
в ближайших планах:
- починить звук и свет
- избавиться от слова "интерсный" в описании примерно всего 😄
- сделать много новых видео по разным темам
👍10❤2
Находки в опенсорсе pinned «всем привет! я очень долго обещал сделать бесплатный курс на ютюбе для всех желающих. и вот я, наконец, начал его делать! 🎉 встречайте: https://www.youtube.com/@sobolevn уникальность формата в том, что рассматриваю одну узкую тему с трех уровней сложности:…»
привет!
в среду 10 июля играем в IT-шную опенсорсную настолку Ship IT в хорошей компании!
ссылка на игру: https://github.com/sobolevn/ship-it-boardgame
в программе:
- душные ITшные шутки
- специальный набор карт для взаимодействия со зрителями
- конкурс на самый смешную шутку в комментариях (приз: физическая настолка!)
участвуют:
- Аня Курносова Pytup: https://xn--r1a.website/+Bz-uVcXh4Jk1YTNi
- Алексей Пирогов https://github.com/astynax
- Денис Пушкарев https://github.com/zloirock
- Паша Коршиков https://xn--r1a.website/tech_meetup
- Олег Чирухин https://github.com/olegchir
ведущий: Никита Соболев https://github.com/sobolevn
начало: 19:30 по МСК
ссылка на трансляцию: https://www.youtube.com/watch?v=pR8tQaoOitc
в среду 10 июля играем в IT-шную опенсорсную настолку Ship IT в хорошей компании!
ссылка на игру: https://github.com/sobolevn/ship-it-boardgame
в программе:
- душные ITшные шутки
- специальный набор карт для взаимодействия со зрителями
- конкурс на самый смешную шутку в комментариях (приз: физическая настолка!)
участвуют:
- Аня Курносова Pytup: https://xn--r1a.website/+Bz-uVcXh4Jk1YTNi
- Алексей Пирогов https://github.com/astynax
- Денис Пушкарев https://github.com/zloirock
- Паша Коршиков https://xn--r1a.website/tech_meetup
- Олег Чирухин https://github.com/olegchir
ведущий: Никита Соболев https://github.com/sobolevn
начало: 19:30 по МСК
ссылка на трансляцию: https://www.youtube.com/watch?v=pR8tQaoOitc
GitHub
GitHub - sobolevn/ship-it-boardgame: Social and fun boardgame about IT. Best with 🍻
Social and fun boardgame about IT. Best with 🍻. Contribute to sobolevn/ship-it-boardgame development by creating an account on GitHub.
👍3
Находки в опенсорсе pinned «привет! в среду 10 июля играем в IT-шную опенсорсную настолку Ship IT в хорошей компании! ссылка на игру: https://github.com/sobolevn/ship-it-boardgame в программе: - душные ITшные шутки - специальный набор карт для взаимодействия со зрителями - конкурс…»
Привет!
Давайте знакомиться заново.
Меня зовут Никита, я опенсорс разработчик: https://github.com/sobolevn
Я люблю компиляторы, тайпчекеры, системы тестирования и статические анализаторы разных видов.
Я разрабатываю множество инструментов, которыми вы уже 100% пользуетесь.
Концепция канала поменялась. О чем тут теперь будет?
- Больше я не буду закидывать безличную информацию о других опенсорс проектах, но буду больше рассказывать про те, где я принимаю активное участие: CPython, mypy, typeshed, Django и тд
- Буду делиться видео своих и чужих интересных докладов
- Публиковать материалы для "Лучшего курса по Питону": https://www.youtube.com/@sobolevn
- Рассказывать про интересные проекты, которые мне встретились в опенсорсе, прикоснуться к которым действительно удалось
- Делиться знаниями про сложные и интересные штуки в моей работе :)
Поддержать мою работу можно тут: https://boosty.to/sobolevn
Давайте знакомиться заново.
Меня зовут Никита, я опенсорс разработчик: https://github.com/sobolevn
Я люблю компиляторы, тайпчекеры, системы тестирования и статические анализаторы разных видов.
Я разрабатываю множество инструментов, которыми вы уже 100% пользуетесь.
Концепция канала поменялась. О чем тут теперь будет?
- Больше я не буду закидывать безличную информацию о других опенсорс проектах, но буду больше рассказывать про те, где я принимаю активное участие: CPython, mypy, typeshed, Django и тд
- Буду делиться видео своих и чужих интересных докладов
- Публиковать материалы для "Лучшего курса по Питону": https://www.youtube.com/@sobolevn
- Рассказывать про интересные проекты, которые мне встретились в опенсорсе, прикоснуться к которым действительно удалось
- Делиться знаниями про сложные и интересные штуки в моей работе :)
Поддержать мою работу можно тут: https://boosty.to/sobolevn
GitHub
sobolevn - Overview
sobolevn has 616 repositories available. Follow their code on GitHub.
🔥93👍27❤12👏6😱1🙏1
Находки в опенсорсе pinned «Привет! Давайте знакомиться заново. Меня зовут Никита, я опенсорс разработчик: https://github.com/sobolevn Я люблю компиляторы, тайпчекеры, системы тестирования и статические анализаторы разных видов. Я разрабатываю множество инструментов, которыми вы уже…»
А еще я включил реакции и комментарии, наслаждайтесь! Правила: https://gist.github.com/sobolevn/d9a598a23e6bb89e51ada71033e9103f
Gist
@opensource_findings: правила
@opensource_findings: правила. GitHub Gist: instantly share code, notes, and snippets.
❤62🔥34🥰10💩5😱4🎉4👏3🤩3🕊2🤯1😢1
И сразу пример нового контента.
За последние два дня добавил поддержку двух новых типов в hypothesis:
- ReadOnly: https://github.com/HypothesisWorks/hypothesis/pull/4072
- LiteralString: https://github.com/HypothesisWorks/hypothesis/pull/4075
Что такое hypothesis? Такой фреймворк для тестирования на основе идеи Property-Based Testing: когда вы тестируете не конкретные значения, а целые законы или свойства вашей системы.
Например:
Что очень удобно,
Hypothesis является частью CI самого CPython: https://github.com/python/cpython/blob/main/Lib/test/support/hypothesis_helper.py и является очень крутой технологией.
На ее основе можно делать безумные вещи: https://sobolevn.me/2021/02/make-tests-a-part-of-your-app
За последние два дня добавил поддержку двух новых типов в hypothesis:
- ReadOnly: https://github.com/HypothesisWorks/hypothesis/pull/4072
- LiteralString: https://github.com/HypothesisWorks/hypothesis/pull/4075
Что такое hypothesis? Такой фреймворк для тестирования на основе идеи Property-Based Testing: когда вы тестируете не конкретные значения, а целые законы или свойства вашей системы.
Например:
import datetime as dt
import attrs
@attrs.define
class User:
# ...
joined_at: dt.datetime
last_loggined_at: dt.datetime
def create_our_user() -> User:
... # your business logic
# Testing:
from hypothesis import given, strategies as st
@given(st.builds(create_our_user))
def test_user_login_cannot_be_after_created(user: User) -> None:
assert user.last_loggined_at >= user.joined_at
Что очень удобно,
st.from_type(...) может просто по аннотациям генерировать вам объекты; потому поддержка новых типов - всегда полезна.Hypothesis является частью CI самого CPython: https://github.com/python/cpython/blob/main/Lib/test/support/hypothesis_helper.py и является очень крутой технологией.
На ее основе можно делать безумные вещи: https://sobolevn.me/2021/02/make-tests-a-part-of-your-app
GitHub
GitHub - HypothesisWorks/hypothesis: The property-based testing library for Python
The property-based testing library for Python. Contribute to HypothesisWorks/hypothesis development by creating an account on GitHub.
👍36😱8🔥3
Одна из самых проблемных частей CPython – вызов Python кода из С.
Делать такое нужно довольно регулярно. Примеры использований:
- Обращение к магическим методам объектов:
- Вызов переданных Python callback'ов:
- Создание новых объектов:
Но, такое всегда нужно делать осторожно. Буквально, почти весь стейт внутри C может измениться после вызова любого Python кода!
Например, такой простой код вызовет
Мне нравится править такое, одно из самых интересных направлений:
- https://github.com/python/cpython/pull/120442
- https://github.com/python/cpython/pull/120303
А вот как такое находить?
1. Внутри CPython есть свой фаззер: https://github.com/python/cpython/tree/main/Modules/_xxtestfuzz Иногда он находит код, который крашит какой-то кусок. Было довольно много полезных срабатываний
2. Есть отдельные инструменты и команды по всему миру, кто заинтересован в разметке исходников CPython и выявлении таких проблем статически
3. Собирать баги от пользователей :(
Если видите crash – бегом репортить багу!
Делать такое нужно довольно регулярно. Примеры использований:
- Обращение к магическим методам объектов:
PyObject_RichCompare, PyObject_GetIter, PyIter_Next, PyObject_GetItem, и тд- Вызов переданных Python callback'ов:
PyObject_Call*, PyObject_Vectorcall, и тд- Создание новых объектов:
PyObject_NewНо, такое всегда нужно делать осторожно. Буквально, почти весь стейт внутри C может измениться после вызова любого Python кода!
Например, такой простой код вызовет
[1] 88503 segmentation fault python на версиях <3.12.5
class evil:
def __init__(self, lst):
self.lst = lst
def __iter__(self):
yield from self.lst
self.lst.clear()
lst = list(range(10))
lst[::-1] = evil(lst)
Мне нравится править такое, одно из самых интересных направлений:
- https://github.com/python/cpython/pull/120442
- https://github.com/python/cpython/pull/120303
А вот как такое находить?
1. Внутри CPython есть свой фаззер: https://github.com/python/cpython/tree/main/Modules/_xxtestfuzz Иногда он находит код, который крашит какой-то кусок. Было довольно много полезных срабатываний
2. Есть отдельные инструменты и команды по всему миру, кто заинтересован в разметке исходников CPython и выявлении таких проблем статически
3. Собирать баги от пользователей :(
Если видите crash – бегом репортить багу!
GitHub
gh-120384: Fix array-out-of-bounds crash in `list_ass_subscript` by sobolevn · Pull Request #120442 · python/cpython
Thank a lot to @MechanicPig for the reproducer and detailed bug description.
Issue: Array out of bounds assignment in list_ass_subscript #120384
Issue: Array out of bounds assignment in list_ass_subscript #120384
🔥35👍5😱3👏2
История одного PR #prhistory
Некоторое время назад я добавил в mypy поддержку
PR большой, его будут смотреть еще некоторое время. Но, о самых важных принципах рассказать можно уже сейчас.
1. Что такое
PEP: https://peps.python.org/pep-0705
По сути, он запрещает такой код:
Крайне полезная вещь для бизнес логики.
2.
Однако, он был добавлен в
Так что пользоваться
3. Как устроен
Основная сложность, что разные special form'ы могут идти вместе:
-
-
- и тд в любых комбинациях
Внутри
4. Какие делатали типизации важны?
Помимо очевидного запрета на изменение
Пример:
Но почему?
Потому что тело функции
Таким образом – мы могли бы допустить ошибку и изменить "неизменяемый" ключ.
Ждём? 🤔
Некоторое время назад я добавил в mypy поддержку
ReadOnly special form для TypedDict: https://github.com/python/mypy/pull/17644PR большой, его будут смотреть еще некоторое время. Но, о самых важных принципах рассказать можно уже сейчас.
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.
I will add docs in a separate PR.
🔥49👍14🤔3👎1😱1
Сегодня говорим про
Вышел восьмой урок "Лучшего курса по Питону": https://www.youtube.com/watch?v=RbznhbK3vC0
Что вообще такое "Лучший курс по Питону"?
- Я решил разобрать все исходники CPython и показать, как на самом деле работают все его части
- В каждом видео я рассказываю про одну узкую тему
- Каждое видео я делю на три уровня сложности: для джунов, мидлов и сениоров
- Переодически беру интервью у других core-разработчиков CPython про разные части интерпретатора в их зоне интересов
- Получается очень хардкорно!
Например, в
Как устроен
Дополнительные материалы (не вошли в выпуск):
- mypy: bytes и bytearray c
- Мутабельность
-
-
Поддержать проект можно тут: https://boosty.to/sobolevn
#лкпп #python #c
bytes!Вышел восьмой урок "Лучшего курса по Питону": https://www.youtube.com/watch?v=RbznhbK3vC0
Что вообще такое "Лучший курс по Питону"?
- Я решил разобрать все исходники CPython и показать, как на самом деле работают все его части
- В каждом видео я рассказываю про одну узкую тему
- Каждое видео я делю на три уровня сложности: для джунов, мидлов и сениоров
- Переодически беру интервью у других core-разработчиков CPython про разные части интерпретатора в их зоне интересов
- Получается очень хардкорно!
Например, в
bytes я показываю, как устроен PyBytesObject (он простой):
typedef struct {
PyObject_VAR_HEAD
Py_DEPRECATED(3.11) Py_hash_t ob_shash;
char ob_sval[1];
/* Invariants:
* ob_sval contains space for 'ob_size+1' elements.
* ob_sval[ob_size] == 0.
* ob_shash is the hash of the byte string or -1 if not computed yet.
*/
} PyBytesObject;
Как устроен
Buffer протокол для bytes с его __buffer__ и __release_buffer__:
static int
bytes_buffer_getbuffer(PyBytesObject *self, Py_buffer *view, int flags)
{
return PyBuffer_FillInfo(view, (PyObject*)self, (void *)self->ob_sval, Py_SIZE(self), 1, flags);
}
static PyBufferProcs bytes_as_buffer = {
(getbufferproc)bytes_buffer_getbuffer,
NULL,
};
Дополнительные материалы (не вошли в выпуск):
- mypy: bytes и bytearray c
disable_bytearray_promotion и disable_memoryview_promotion https://github.com/python/mypy/commit/2d70ac0b33b448d5ef51c0856571068dd0754af6- Мутабельность
bytes https://docs.python.org/3.13/c-api/bytes.html#c._PyBytes_Resize-
PyBytes_Writer API: https://github.com/capi-workgroup/decisions/issues/39-
ob_shash deprecation: https://github.com/python/cpython/issues/91020Поддержать проект можно тут: https://boosty.to/sobolevn
#лкпп #python #c
YouTube
Лучший курс по Python 8: bytes
Лучший курс по питону: 8
Или "обзор исходников CPython с CPython core разработчиком".
Тема: bytes
- Магические методы bytes: __bytes__, __buffer__, __release_buffer__
- Способы записи bytes
- bytes и collections.abc: Sequence, Buffer, bytes_iterator
- bytes…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: bytes
- Магические методы bytes: __bytes__, __buffer__, __release_buffer__
- Способы записи bytes
- bytes и collections.abc: Sequence, Buffer, bytes_iterator
- bytes…
🔥51❤15👍1👏1
Одна из самых сложных частей в устройстве mypy – type narrowing.
Что такое type narrowing? По-русски оно называется "сужение типа". Например:
Тут все достаточно очевидно. Следующие важные части для сужения типов:
-
-
Пример:
Есть еще много разных механизмов для сужения, подробно можно посмотреть тут: https://github.com/python/mypy/blob/fe15ee69b9225f808f8ed735671b73c31ae1bed8/mypy/checker.py#L5805
Но сейчас поговорим про новую фичу, которая скоро появится в mypy.
Допустим у вас есть такой код:
После моего PR (https://github.com/python/mypy/pull/17678) вызов данной функции и сужение типа будут работать корректно:
Данная функция, например, используется для типизации встроенной функции
Сейчас ей требуется грязный хак с
Дальше – я его уберу.
В практических задачах данный подход может использоваться для создания typesafe бизнес логики, которая сужает типы ваших бизнес объектов по условию:
В комментах можно обсудить: приходилось ли вам использовать type narrowing для решения ваших бизнес задач? Помогает ли такой подход в написании более надежного кода?
#prhistory
Что такое type narrowing? По-русски оно называется "сужение типа". Например:
def accepts_both(arg: int | str):
reveal_type(arg) # int | str
if isinstance(arg, int):
reveal_type(arg) # int
else:
reveal_type(arg) # str
Тут все достаточно очевидно. Следующие важные части для сужения типов:
-
TypeGuard – определяет, каким будет тип в if при вызове функции-проверки. Почти не имеет ограничений.-
TypeIs – определяет, каким будет тип в if при вызове функции-проверки и что будет в else. Имеет множество ограничений.Пример:
from typing import TypeIs
from my_project import Schema
def is_schema(obj: object) -> TypeIs[Schema]:
return hasattr(obj, "_schema") # actually returns `bool`
def accepts_both(obj: str | Schema):
reveal_type(arg) # str | Schema
if is_schema(arg):
reveal_type(arg) # Schema
else:
reveal_type(arg) # str
Есть еще много разных механизмов для сужения, подробно можно посмотреть тут: https://github.com/python/mypy/blob/fe15ee69b9225f808f8ed735671b73c31ae1bed8/mypy/checker.py#L5805
Но сейчас поговорим про новую фичу, которая скоро появится в mypy.
TypeIs / TypeGuard + @overload 😱Допустим у вас есть такой код:
from typing import Any, TypeIs, overload
@overload
def func1(x: str) -> TypeIs[str]:
...
@overload
def func1(x: int) -> TypeIs[int]:
...
def func1(x: Any) -> Any:
return True # does not matter
После моего PR (https://github.com/python/mypy/pull/17678) вызов данной функции и сужение типа будут работать корректно:
def accepts_both(val: Any):
if func1(val):
reveal_type(val) # N: Revealed type is "Union[builtins.int, builtins.str]"
else:
reveal_type(val) # N: Revealed type is "Any"
Данная функция, например, используется для типизации встроенной функции
dataclasses.is_dataclass: https://github.com/python/typeshed/blob/53be87bbb45d0b294a4f5b12683e7684e20032d9/stdlib/dataclasses.pyi#L217-L223Сейчас ей требуется грязный хак с
Never в первом @overload:
# HACK: `obj: Never` typing matches if object argument is using `Any` type.
@overload
def is_dataclass(obj: Never) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ... # type: ignore[narrowed-type-not-subtype] # pyright: ignore[reportGeneralTypeIssues]
@overload
def is_dataclass(obj: type) -> TypeIs[type[DataclassInstance]]: ...
@overload
def is_dataclass(obj: object) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ...
Дальше – я его уберу.
В практических задачах данный подход может использоваться для создания typesafe бизнес логики, которая сужает типы ваших бизнес объектов по условию:
from typing import TypeIs, overload
from my_app.models import User, PaidUser, FreeUser # User and its subclasses
from my_app.models import Subscription
@overload
def is_paid_user(user: User, subscription: None) -> TypeIs[FreeUser]:
...
@overload
def is_paid_user(user: User, subscription: Subscription) -> TypeIs[PaidUser]:
...
В комментах можно обсудить: приходилось ли вам использовать type narrowing для решения ваших бизнес задач? Помогает ли такой подход в написании более надежного кода?
#prhistory
GitHub
mypy/mypy/checker.py at fe15ee69b9225f808f8ed735671b73c31ae1bed8 · python/mypy
Optional static typing for Python. Contribute to python/mypy development by creating an account on GitHub.
👍35🔥8❤3
Проблемы модуля `inspect`.
Модуль
Если вы не любите людей, то можете спрашивать их:
1. Чем отличается
2. Какие проблемы есть у
3. Чем отличаются
4. В чем разница между
5. Чем будет отличаться
6. Как конкретно работает получение сигнатуры у разных объектов? 😱
Некоторое время назад я взялся исправить несколько самых сломанных частей: https://github.com/python/cpython/issues/108901
И даже сделал пакет с бекпортами для <=3.13: https://github.com/wemake-services/inspect313
Но все опять оказалось совсем не просто. Я не успел до фича фриза в 3.13, так что надеюсь, что успею в 3.14
Что сломано?
Например:
Должно быть так:
Но, возникает вопрос: а нужно ли вообще добавлять такой метод? Насколько полезено получать сигнатуры из фреймов и код-обжектов?
Далее:
Но, все-таки работа ведется довольно активно:
-
- Добавили
- Пофиксили кучу багов
Для чего `inspect` можно использовать на практике?
Я пользовался
Довольно много библиотечного кода используют
- https://github.com/search?type=code&q=inspect.iscoroutinefunction
- https://github.com/search?type=code&q=inspect.getfullargspec
- https://github.com/search?type=code&q=inspect.getargvalues
Расскажите: а у вас были проблемы с
Модуль
inspect в питоне – сборник костылей и легаси. Если вы не любите людей, то можете спрашивать их:
1. Чем отличается
typing.get_type_hints от inspect.get_annotations? А от annotationslib.get_annotations?2. Какие проблемы есть у
getargvalues?3. Чем отличаются
getargs, getfullargspec и singature?4. В чем разница между
inspect.iscoroutinefunction и asyncio.iscoroutinefunction? А между inspect.iscoroutine и asyncio.iscoroutine?5. Чем будет отличаться
inspect.getmembers от inspect.getmembers_static?6. Как конкретно работает получение сигнатуры у разных объектов? 😱
Некоторое время назад я взялся исправить несколько самых сломанных частей: https://github.com/python/cpython/issues/108901
И даже сделал пакет с бекпортами для <=3.13: https://github.com/wemake-services/inspect313
Но все опять оказалось совсем не просто. Я не успел до фича фриза в 3.13, так что надеюсь, что успею в 3.14
Что сломано?
Например:
inspect.getargvalues. Оно не работает с pos-only параметрами:
>>> import inspect
>>> def func(a: int = 0, /, b: int = 1, *, c: int = 2):
... return inspect.currentframe()
>>> frame = func()
>>> # notice that pos-only and kw-only args are not supported properly:
>>> inspect.formatargvalues(*inspect.getargvalues(frame))
'(a=0, b=1, c=2)'
Должно быть так:
>>> from inspect import Signature
>>> str(Signature.from_frame(frame)) # this API does not exist yet
'(a=0, /, b=1, *, c=2)'
Но, возникает вопрос: а нужно ли вообще добавлять такой метод? Насколько полезено получать сигнатуры из фреймов и код-обжектов?
Далее:
getfullargspec. Он не поддерживает pos-only параметры и не совсем корректно работает с параметрами self, cls, тд.
>>> import inspect
>>> class A:
... def method(self, arg, /): ...
>>> inspect.getfullargspec(A.method)
FullArgSpec(args=['self', 'arg'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})
>>> inspect.getfullargspec(A().method).args # must not report `self`! :(
['self', 'arg']
>>> inspect.signature(A.method)
<Signature (self, arg, /)>
>>> inspect.signature(A().method)
<Signature (arg, /)>
Но, все-таки работа ведется довольно активно:
-
asyncio.iscoroutinefunction уже задепрекейчена: https://github.com/python/cpython/pull/122875 Скоро будет только версия из inspect- Добавили
annotationslib.get_annotations (которая переехала из inspect и теперь будет самым-правильным-способом™): https://github.com/python/cpython/blob/9e108b8719752a0a2e390eeeaa8f52391f75120d/Lib/annotationlib.py#L582 - Пофиксили кучу багов
Для чего `inspect` можно использовать на практике?
Я пользовался
inspect.signature только для создания рантайм имплементациия каррирования для dry-python/returns: https://github.com/dry-python/returns/blob/master/returns/curry.pyДовольно много библиотечного кода используют
inspect для интроспекции в самых неожиданных местах:- https://github.com/search?type=code&q=inspect.iscoroutinefunction
- https://github.com/search?type=code&q=inspect.getfullargspec
- https://github.com/search?type=code&q=inspect.getargvalues
Расскажите: а у вас были проблемы с
inspect? Если да, то какие?GitHub
Add modern alternatives to `inspect` module, deprecate old incorrect APIs · Issue #108901 · python/cpython
Feature or enhancement Proposal: I propose to provide modern alternatives to and deprecate these inspect members: getargs() undocumented helper used in getargvalues. It works with __code__ objects....
🔥26😁3👍2❤1
`slots=True` ломает ваши датаклассы!
Когда прям с заголовка набросил, то дальше уже всегда проще.
Давайте посмотрим, какую пользу и какой вред приносит использование
Во-первых, что делает
1. Валидирует, что
2. Генерирует дескрипторы для всех имен в
3. Если слот
Из-за чего больше нельзя будет назначать произвольные атрибуты:
Но, достаточно добавить
4.
Разница в скорости доступа не очень большая, но есть:
Против доступа со
Так что там с датаклассами?
Штука в том, что создать слоты в существующем классе – нельзя физически. Слишком много всего написано на C.
Можно только пересоздать класс еще раз 😱
https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Lib/dataclasses.py#L1224-L1276
Что порождает много проблем. Например:
- Нельзя использовать
Потому что
- Нельзя использовать `__init_subclass__` в
Так что - будьте осторожны!
Когда прям с заголовка набросил, то дальше уже всегда проще.
Давайте посмотрим, какую пользу и какой вред приносит использование
@dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много.Во-первых, что делает
__slots__ = ('a',) внутри класса?
class My:
__slots__ = ('a',)
1. Валидирует, что
__slots__ корректны2. Генерирует дескрипторы для всех имен в
__slots__, см https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Objects/descrobject.c#L793-L796
>>> class My:
... __slots__ = ('a',)
...
>>> My.a, type(My.a)
(<member 'a' of 'My' objects>, <class 'member_descriptor'>)
3. Если слот
__dict__ не проставлен, то меняет базовую функцию доступа к и установки аттрибутов
/* Special case some slots */
if (type->tp_dictoffset != 0 || ctx->nslot > 0) {
PyTypeObject *base = ctx->base;
if (base->tp_getattr == NULL && base->tp_getattro == NULL) {
type->tp_getattro = PyObject_GenericGetAttr;
}
if (base->tp_setattr == NULL && base->tp_setattro == NULL) {
type->tp_setattro = PyObject_GenericSetAttr;
}
}
Из-за чего больше нельзя будет назначать произвольные атрибуты:
>>> class My:
... __slots__ = ('a',)
...
>>> m = My()
>>> m.custom = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'My' object has no attribute 'custom'
Но, достаточно добавить
'__dict__' внутрь __slots__, чтобы вернуть данное поведение: __slots__ = ('a', '__dict__'):
>>> class My:
... __slots__ = ('a', '__dict__')
...
>>> m = My()
>>> m.custom = 0
4.
__slots__ ускоряет доступ к атрибутам и уменьшает размер объектов.
>>> import sys
>>> class A:
... __slots__ = ('a',)
... def __init__(self, a):
... self.a = a
>>> class B:
... def __init__(self, a):
... self.a = a
>>> sys.getsizeof(A(1))
40
>>> sys.getsizeof(B(1))
56
Разница в скорости доступа не очень большая, но есть:
» pyperf timeit -s '
class A:
def __init__(self, a):
self.a = a
a = A(1)' 'a.a'
.....................
Mean +- std dev: 13.9 ns +- 0.1 ns
Против доступа со
__slots__:
» pyperf timeit -s '
. class A:
. __slots__ = ("a",)
. def __init__(self, a):
. self.a = a
.
. a = A(1)' 'a.a'
.....................
Mean +- std dev: 13.3 ns +- 0.1 ns
Так что там с датаклассами?
Штука в том, что создать слоты в существующем классе – нельзя физически. Слишком много всего написано на C.
Можно только пересоздать класс еще раз 😱
https://github.com/python/cpython/blob/91ff700de28f3415cbe44f58ce84a2670b8c9f15/Lib/dataclasses.py#L1224-L1276
cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
Что порождает много проблем. Например:
- Нельзя использовать
super() без параметров в методах внутри тела класса:
>>> @dataclass(slots=True)
... class My:
... def __str__(self) -> str:
... return super().__str__()
...
>>> str(My())
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
str(My())
~~~^^^^^^
File "<python-input-1>", line 4, in __str__
return super().__str__()
^^^^^^^^^^^^^^^
TypeError: super(type, obj): obj (instance of My) is not an instance or subtype of type (My).
Потому что
__str__.closure не обновляет cell объекты на другой класс при пересоздании. Есть PR, но все сложно: https://github.com/python/cpython/pull/111538- Нельзя использовать `__init_subclass__` в
родителях класса со slots, где ожидаются параметры. Тут только документацией можно помочь: https://github.com/python/cpython/pull/123342Так что - будьте осторожны!
🔥35👍13🤔7❤5🤯3🤡3
Материалы с PythoNN 30 августа 2024
Мы проводим питон митапы в Нижнем Новгороде раз в квартал уже несколько лет.
30 августа к нам приезжало два замечательных гостя:
- Александр Гончаров – "Это вообще не просто!" https://www.youtube.com/watch?v=0EFHpmEgXak
- Андрей Пронин – "Как увеличить зарплату в два раза за год?" https://www.youtube.com/watch?v=IfLT_ssxOhU
Внутри есть ссылки и на презентации к докладам, и на личные страницы гостей.
Друзья, спасибо большое за интересные доклады. Спасибо гостям за отличную атмосферу и интересные вопросы.
Если хотите побывать в НН и заодно сделать доклад – пишите, буду рад помочь с подготовкой!
Мы проводим питон митапы в Нижнем Новгороде раз в квартал уже несколько лет.
30 августа к нам приезжало два замечательных гостя:
- Александр Гончаров – "Это вообще не просто!" https://www.youtube.com/watch?v=0EFHpmEgXak
- Андрей Пронин – "Как увеличить зарплату в два раза за год?" https://www.youtube.com/watch?v=IfLT_ssxOhU
Внутри есть ссылки и на презентации к докладам, и на личные страницы гостей.
Друзья, спасибо большое за интересные доклады. Спасибо гостям за отличную атмосферу и интересные вопросы.
Если хотите побывать в НН и заодно сделать доклад – пишите, буду рад помочь с подготовкой!
🔥34👏5😱1
Находки в опенсорсе
`slots=True` ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
Продолжаем ломать dataclass'ы со `__slots__`!
Некоторое время назад прилетел баг: https://github.com/python/cpython/issues/118033
Причина? Причина нам пока не очень понятна. Давайте разбираться. Баг состоит из нескольких частей.
Weakref
Что вообще такое
Следовательно: когда мы создаем объект со слотами, нам необходимо, чтобы слот
PEP 695
В Python3.12, как мы все знаем, добавили новый синтаксис для
Чтобы реализовать данную возможность, часть кода была переписана с Python на C. Например, появились такие файлы как:
- https://github.com/python/cpython/blob/main/Objects/typevarobject.c
- https://github.com/python/cpython/blob/main/Include/internal/pycore_typevarobject.h
- https://github.com/python/cpython/blob/main/Modules/_typingmodule.c
Значит, что в Python3.12
А значит, что у них поменялись внутренности устройства.
И теперь классам с
Следовательно, чтобы правильно работали слоты – нужно проверить родителя.
Offsets
В сишных типах обычно не объявляют
Что за слоты такие?
Если вы смотрите "Лучший курс по питону", то вы их уже много раз видели (а если не смотрите, то почему?!). Слоты =
-
-
- Полный список: https://docs.python.org/3/c-api/typeobj.html#tp-slots
Есть несколько специальных слотов, которые нас сегодня интересуют:
- Слот
- Слот
Посмотрим:
Смотрим на разницу:
Фатальное изменение
И последняя часть. К нам пришел вот такой PR: https://github.com/python/cpython/commit/a22d05f04c074dbb4f71e7837f54c0bb693db75d
Некоторое время назад прилетел баг: https://github.com/python/cpython/issues/118033
from dataclasses import dataclass
@dataclass(slots=True, weakref_slot=True)
class Token[T]:
ctx: T
print(hasattr(Token, '__weakref__'))
# 3.12.2: True
# 3.12.3: False
Причина? Причина нам пока не очень понятна. Давайте разбираться. Баг состоит из нескольких частей.
Weakref
Что вообще такое
__weakref__? Конечно же, оно связано с модулем weakref для создания слабых ссылок, которые не увеличивают ob_refcnt объекта. Внутри __weakref__ будет хранится объект ссылки. Смотрим:
>>> class My: ...
...
>>> import weakref
>>> m = My()
>>> w = weakref.ref(m)
>>> m.__weakref__
<weakref at 0x103c77d20; to 'My' at 0x103be9920>
>>> m.__weakref__ is w
True
Следовательно: когда мы создаем объект со слотами, нам необходимо, чтобы слот
__weakref__ существовал. Иначе – будет ошибка:
>>> class WithSlots:
... __slots__ = () # no '__weakref__'
...
>>> weakref.ref(WithSlots())
TypeError: cannot create weak reference to 'WithSlots' object
PEP 695
В Python3.12, как мы все знаем, добавили новый синтаксис для
TypeVar, ParamSpec, TypeVarTuple и Generic классов, функций и алиасов.Чтобы реализовать данную возможность, часть кода была переписана с Python на C. Например, появились такие файлы как:
- https://github.com/python/cpython/blob/main/Objects/typevarobject.c
- https://github.com/python/cpython/blob/main/Include/internal/pycore_typevarobject.h
- https://github.com/python/cpython/blob/main/Modules/_typingmodule.c
Значит, что в Python3.12
Generic стал С типом.А значит, что у них поменялись внутренности устройства.
И теперь классам с
[] автоматически назначается родитель: _typing.Generic
>>> class Example[T]: ...
...
>>> Example.__bases__
(<class 'typing.Generic'>,)
Следовательно, чтобы правильно работали слоты – нужно проверить родителя.
Offsets
В сишных типах обычно не объявляют
__slots__, потому что используют другие - сишные - слоты.Что за слоты такие?
Если вы смотрите "Лучший курс по питону", то вы их уже много раз видели (а если не смотрите, то почему?!). Слоты =
tp_* места для вставки разных обработчиков под разные случаи жизни. Например:-
tp_new для __new__-
tp_richcompare для сравнений >, <, тд- Полный список: https://docs.python.org/3/c-api/typeobj.html#tp-slots
Есть несколько специальных слотов, которые нас сегодня интересуют:
- Слот
tp_dictoffset или макро Py_TPFLAGS_MANAGED_DICT, который указывает, что у объекта есть __dict__- Слот
tp_weakrefoffset или макро Py_TPFLAGS_MANAGED_WEAKREF, который указывает, что у объекта есть __weakref__Посмотрим:
PyType_Spec typevar_spec = {
.name = "typing.TypeVar",
.flags = ... | Py_TPFLAGS_MANAGED_DICT | Py_TPFLAGS_MANAGED_WEAKREF,
};
// vs
PyType_Spec generic_spec = {
.name = "typing.Generic",
// No `__dictoffset__` and no `__weakrefoffset__`:
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
};
Смотрим на разницу:
>>> from _typing import TypeVar, Generic
>>> TypeVar.__dictoffset__, TypeVar.__weakrefoffset__
(-1, -32)
>>> Generic.__dictoffset__, Generic.__weakrefoffset__
(0, 0)
>>> weakref.ref(TypeVar('A'))
<weakref at 0x103c77c40; dead>
>>> weakref.ref(Generic())
TypeError: cannot create weak reference to 'typing.Generic' object
Фатальное изменение
И последняя часть. К нам пришел вот такой PR: https://github.com/python/cpython/commit/a22d05f04c074dbb4f71e7837f54c0bb693db75d
def _get_slots(cls):
match cls.__dict__.get('__slots__'):
# A class which does not define __slots__ at all is equivalent
# to a class defining __slots__ = ('__dict__', '__weakref__')
case None:
yield from ('__dict__', '__weakref__')
# ...
👍13❤3🔥3
Находки в опенсорсе
`slots=True` ломает ваши датаклассы! Когда прям с заголовка набросил, то дальше уже всегда проще. Давайте посмотрим, какую пользу и какой вред приносит использование @dataclass(slots=True) или @attr.define(slots=True). В целом - различий не так много. Во…
Который предполагал, что если
Я пофиксил вот так: https://github.com/python/cpython/commit/fa9b9cb11379806843ae03b1e4ad4ccd95a63c02
Теперь мы правильно учитываем наличие C слотов в том числе. И правильно определяем, что у базового класса
Дело закрыто! Ну как вам погружение? :)
__slots__ явно не объявлены у типа, то по-умолчанию стоят __dict__ и __weakref__. Что правда для Python типов, но нельзя забывать про C типы, как я показывал выше.Я пофиксил вот так: https://github.com/python/cpython/commit/fa9b9cb11379806843ae03b1e4ad4ccd95a63c02
def _get_slots(cls):
match cls.__dict__.get('__slots__'):
# `__dictoffset__` and `__weakrefoffset__` can tell us whether
# the base type has dict/weakref slots, in a way that works correctly
# for both Python classes and C extension types. Extension types
# don't use `__slots__` for slot creation
case None:
slots = []
if getattr(cls, '__weakrefoffset__', -1) != 0:
slots.append('__weakref__')
if getattr(cls, '__dictrefoffset__', -1) != 0:
slots.append('__dict__')
yield from slots
Теперь мы правильно учитываем наличие C слотов в том числе. И правильно определяем, что у базового класса
Generic нет слота __weakref__. И нам нужно его добавить в наш новый датакласс. Дело закрыто! Ну как вам погружение? :)
1🤯28🔥11👍6
Находки в опенсорсе
Который предполагал, что если __slots__ явно не объявлены у типа, то по-умолчанию стоят __dict__ и __weakref__. Что правда для Python типов, но нельзя забывать про C типы, как я показывал выше. Я пофиксил вот так: https://github.com/python/cpython/commit…
Внимательный читатель (спасибо, Вася) заметил, что у меня ОПЕЧАТКА В ФИКСЕ. там написано
https://github.com/python/cpython/issues/123935
Данный кейс не был протестирован. И сейчас я уже отправляют ЕЩЕ ОДИН PR, теперь уже точно финальный.
Вот и польза от поста 🌚️️
__dictrefoffset__, а не __dictoffset__, как должно быть. https://github.com/python/cpython/issues/123935
Данный кейс не был протестирован. И сейчас я уже отправляют ЕЩЕ ОДИН PR, теперь уже точно финальный.
Вот и польза от поста 🌚️️
GitHub
Incorrect slot check: typo in `__dictoffest__` · Issue #123935 · python/cpython
Bug report I made a typo that made it into the final code: cpython/Lib/dataclasses.py Lines 1211 to 1212 in 00ffdf2 if getattr(cls, '__dictrefoffset__', -1) != 0: slots.append('__dict__...
🔥56👍12❤1
