Привет!
Давайте знакомиться заново.
Меня зовут Никита, я опенсорс разработчик: 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 619 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❤2
Лучший курс по Python 9: Переменные
https://www.youtube.com/watch?v=crSzQKfevZU
Я хотел сделать видео про переменные, которое бы рассказывало: а как на самом деле происходит создание и поиск имени? Все рассказывают про переменные, как про какие "коробки" для значений. А не они не коробки! Потому, в видео про переменные я рассказываю:
- Что никаких переменных в Python – нет 🌚
- Про
- Про генерацию байткода: покрываем все стадии через
- Про замыкания с
- Ну и про рантайм конечно же! Как работает, например
Бонус! Я показывал видео Грише Петрову до публикации. Он дал ценную обратную связь: я не упомянул, почему иногда модификация
-
На самом деле
будет делать
И изменения видны не будут.
- А вот тут я пропустил шаг: так а почему на уровне модуля / REPL оно работает?
Потому что в REPL / модуле
Где
- Далее, на слайде я показываю, но повторю данную мысль еще раз:
Не надо так! Не модифицируйте скоупы, просто потому что можно!
Надеюсь, что было интересно.
Поддержать такой контент можно тут:
- https://boosty.to/sobolevn
- https://github.com/sponsors/wemake-services
#lkpp
https://www.youtube.com/watch?v=crSzQKfevZU
Я хотел сделать видео про переменные, которое бы рассказывало: а как на самом деле происходит создание и поиск имени? Все рассказывают про переменные, как про какие "коробки" для значений. А не они не коробки! Потому, в видео про переменные я рассказываю:
- Что никаких переменных в Python – нет 🌚
- Про
frame.f_locals и frame.f_globals- Про генерацию байткода: покрываем все стадии через
symtable.c / compile.c / codegen.c- Про замыкания с
.__closure__ и MAKE_CELL- Ну и про рантайм конечно же! Как работает, например
globals() и locals() на самом деле
/*[clinic input]
globals as builtin_globals
Return the dictionary containing the current scope's global variables.
NOTE: Updates to this dictionary *will* affect name lookups in the current
global scope and vice-versa.
[clinic start generated code]*/
static PyObject *
builtin_globals_impl(PyObject *module)
/*[clinic end generated code: output=e5dd1527067b94d2 input=9327576f92bb48ba]*/
{
PyObject *d;
d = PyEval_GetGlobals();
return Py_XNewRef(d);
}
Бонус! Я показывал видео Грише Петрову до публикации. Он дал ценную обратную связь: я не упомянул, почему иногда модификация
locals().update() работает, а иногда нет. Исправляюсь!-
locals(), как показано в видео, обычно возвращает новый dict, потому что использует прокси внутри C. Внутри функции модификация locals() работать не будет. И вот почему, код:
// PyObject * _PyEval_GetFrameLocals(void)
if (PyFrameLocalsProxy_Check(locals)) {
PyObject* ret = PyDict_New();
if (ret == NULL) {
Py_DECREF(locals);
return NULL;
}
if (PyDict_Update(ret, locals) < 0) {
Py_DECREF(ret);
Py_DECREF(locals);
return NULL;
}
Py_DECREF(locals);
return ret;
}
assert(PyMapping_Check(locals));
return locals;
На самом деле
def some():
locals().update({'a': 1})
print(a)
будет делать
.update на *другом* dict объекте, который мы создали из PyFrameLocalsProxy, а не сам прокси.И изменения видны не будут.
- А вот тут я пропустил шаг: так а почему на уровне модуля / REPL оно работает?
Потому что в REPL / модуле
frame.f_locals is frame.f_globals. Вот код:
static int
PyRun_InteractiveOneObjectEx(FILE *fp, PyObject *filename,
PyCompilerFlags *flags)
{
PyArena *arena = _PyArena_New();
if (arena == NULL) {
return -1;
}
mod_ty mod;
PyObject *interactive_src;
int parse_res = pyrun_one_parse_ast(fp, filename, flags, arena, &mod, &interactive_src);
PyObject *main_module = PyImport_AddModuleRef("__main__");
PyObject *main_dict = PyModule_GetDict(main_module); // borrowed ref
PyObject *res = run_mod(mod, filename, main_dict, main_dict, flags, arena, interactive_src, 1);
// ...
Где
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
PyCompilerFlags *flags, PyArena *arena, PyObject* interactive_src,
int generate_new_source)
{
- Далее, на слайде я показываю, но повторю данную мысль еще раз:
NOTE: Whether or not updates to this dictionary will affect name lookups in
the local scope and vice-versa is *implementation dependent* and not
covered by any backwards compatibility guarantees.
Не надо так! Не модифицируйте скоупы, просто потому что можно!
Надеюсь, что было интересно.
Поддержать такой контент можно тут:
- https://boosty.to/sobolevn
- https://github.com/sponsors/wemake-services
#lkpp
YouTube
Лучший курс по Python 9: Переменные
Лучший курс по питону: 9
Или "обзор исходников CPython с CPython core разработчиком".
Тема: переменные
- Что такое "переменная" в Python?
- Есть ли в Python "переменные"?
- Чем "переменная" отличается от "имени"?
- Что такое `locals()` и `globals()` в Python?…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: переменные
- Что такое "переменная" в Python?
- Есть ли в Python "переменные"?
- Чем "переменная" отличается от "имени"?
- Что такое `locals()` и `globals()` в Python?…
3🔥57❤19👍10🤔1🤯1
Атрибут `__class__` в Python можно переписывать! 😱
Пример:
Но как?
Обратимся к исходникам в
Тут можно увидеть сразу несколько важных моментов:
1. Данное действие совсем-совсем не thread-safe, чтобы его совершить приходится останавливать все остальные треды в режиме noGIL
2. У каждого объекта в Python есть поле
Важно, оба класса, должны быть совместимы, иначе будут ошибки:
- Оба класса должны быть мутабельными
- Оба класса должны иметь совместимый binary layout (см функцию
-
Но зачем?
Данный хак используется в большом количестве мест в исходниках.
- Например, такое есть прямо в доках
И его можно менять:
Внутри есть
-
- В
- В
Пример:
>>> class Cat:
... def meow(self):
... print('meow')
>>> class Dog:
... def bark(self):
... print('woof!')
>>> c = Cat()
>>> c.__class__ = Dog # превращаем котика в собачку!
>>> isinstance(c, Dog)
True
>>> c.bark()
woof!
Но как?
Обратимся к исходникам в
typeobject.c:
static PyGetSetDef object_getsets[] = {
{"__class__", object_get_class, object_set_class,
PyDoc_STR("the object's class")},
{0}
};
static PyObject *
object_get_class(PyObject *self, void *closure)
{
return Py_NewRef(Py_TYPE(self));
}
static int
object_set_class(PyObject *self, PyObject *value, void *closure)
{
// ...
PyTypeObject *newto = (PyTypeObject *)value;
#ifdef Py_GIL_DISABLED
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyEval_StopTheWorld(interp);
#endif
PyTypeObject *oldto = Py_TYPE(self);
// Calls:
// ob->ob_type = newto;
int res = object_set_class_world_stopped(self, newto);
#ifdef Py_GIL_DISABLED
_PyEval_StartTheWorld(interp);
#endif
}
Тут можно увидеть сразу несколько важных моментов:
1. Данное действие совсем-совсем не thread-safe, чтобы его совершить приходится останавливать все остальные треды в режиме noGIL
2. У каждого объекта в Python есть поле
ob_type, где хранится его тип. А значит, тип можно менятьВажно, оба класса, должны быть совместимы, иначе будут ошибки:
- Оба класса должны быть мутабельными
- Оба класса должны иметь совместимый binary layout (см функцию
compatible_for_assignment в `typeobject.c`)-
__slots__ должны быть одинаковымиНо зачем?
Данный хак используется в большом количестве мест в исходниках.
- Например, такое есть прямо в доках
Mock: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.__class__
>>> mock = Mock()
>>> mock.__class__ = dict
>>> isinstance(mock, dict)
True
И его можно менять:
@property
def __class__(self):
if self._spec_class is None:
return type(self)
return self._spec_class
Внутри есть
__setattr__, который вместо .__class__ = X будет менять ._spec_class = X. А свойство будет отражать изменение.-
LazyLoader в importlib делает такое: https://github.com/python/cpython/blob/46f5cbca4c37c57f718d3de0d7f7ddfc44298535/Lib/importlib/util.py#L273
def exec_module(self, module):
"""Make the module load lazily."""
# Threading is only needed for lazy loading, and importlib.util can
# be pulled in at interpreter startup, so defer until needed.
import threading
module.__spec__.loader = self.loader
module.__loader__ = self.loader
loader_state = {}
loader_state['__dict__'] = module.__dict__.copy()
loader_state['__class__'] = module.__class__
loader_state['lock'] = threading.RLock()
loader_state['is_loading'] = False
module.__spec__.loader_state = loader_state
module.__class__ = _LazyModule # <---
- В
annotationlib / typing такое используется, что превращать строковое представление аннотаций в ForwardRef: https://github.com/python/cpython/blob/46f5cbca4c37c57f718d3de0d7f7ddfc44298535/Lib/annotationlib.py#L580-L581
class _Stringifier:
# Must match the slots on ForwardRef, so we can turn an instance of one into an
# instance of the other in place.
__slots__ = _SLOTS
# ...
for obj in globals.stringifiers:
assert isinstance(obj, _Stringifier)
obj.__class__ = ForwardRef
- В
threading используется, чтоб _DummyThread мог притворяться MainThread: https://github.com/python/cpython/blob/46f5cbca4c37c57f718d3de0d7f7ddfc44298535/Lib/threading.py#L1419
def _after_fork(self, new_ident=None):
if new_ident is not None:
self.__class__ = _MainThread
self._name = 'MainThread'
self._daemonic = False
Thread._after_fork(self, new_ident=new_ident)
Python documentation
unittest.mock — mock object library
Source code: Lib/unittest/mock.py unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they hav...
1🤯29🔥9👍3🤔3
Практическое применение?
Все мои посты всегда объединяет одно – понятная практическая ценность! 🌚️️️️️️
Не будем же отступать от традиции и здесь.
Зачем такое может понадобиться в реальном проекте? Я вижу две основные задачи:
- Написание кастомных моков / стабов в тестах. Замена
- Можно хачить модули!
Таким образом вы можете менять поведение модулей для каких-то супер специфичных штук помимо
Давайте обсудим: разрешили бы вы такое в своей кодовой базе? И почему нет?
Все мои посты всегда объединяет одно – понятная практическая ценность! 🌚️️️️️️
Не будем же отступать от традиции и здесь.
Зачем такое может понадобиться в реальном проекте? Я вижу две основные задачи:
- Написание кастомных моков / стабов в тестах. Замена
.__class__ в таком случае имеет понятную ценность, что объект делает вид, что он совсем другой объект. Ну и понимание, как работает стандартный Mock() и Mock(spec=X) - Можно хачить модули!
import sys
import types
class VerboseModule(types.ModuleType):
def __setattr__(self, attr, value):
print(f'setting {attr} to {value}')
super().__setattr__(attr, value)
sys.modules[__name__].__class__ = VerboseModule
Таким образом вы можете менять поведение модулей для каких-то супер специфичных штук помимо
__dir__ и __getattr__ на уровне модуляДавайте обсудим: разрешили бы вы такое в своей кодовой базе? И почему нет?
Python documentation
unittest.mock — mock object library
Source code: Lib/unittest/mock.py unittest.mock is a library for testing in Python. It allows you to replace parts of your system under test with mock objects and make assertions about how they hav...
😱23👍11🤔3🤯2❤1😁1
Вышел 3.13-rc3
Новости одной строкой:
- Последний релиз перед 3.13.0
- Официальная дата релиза 3.13 перенесена на 7 октября
- В релизе был ревертнут новый инкрементальный GC (https://github.com/python/cpython/pull/124770), потому что он вызывал регрессии по перформансу. Например:
- Такое уже случалось, первая версия инкрементального GC сделала CPython в примерно 20 раз медленнее (https://github.com/python/cpython/issues/117108)
- Как теперь будет работать
- Ждем новый сборщик мусора в 3.14
Новости одной строкой:
- Последний релиз перед 3.13.0
- Официальная дата релиза 3.13 перенесена на 7 октября
- В релизе был ревертнут новый инкрементальный GC (https://github.com/python/cpython/pull/124770), потому что он вызывал регрессии по перформансу. Например:
sphinx-build стал на 48% медленней (https://github.com/python/cpython/issues/124567)- Такое уже случалось, первая версия инкрементального GC сделала CPython в примерно 20 раз медленнее (https://github.com/python/cpython/issues/117108)
- Как теперь будет работать
nogil со старым сборщиком – я пока не понимаю 🤔️️️️️️- Ждем новый сборщик мусора в 3.14
🔥31👍18😱5
