Статический анализ GitHub Actions
Сразу после релиза новой версии линтера, я задался вопросом обновления своего шаблона для создания новых питоновских библиотек: https://github.com/wemake-services/wemake-python-package
И я понял, что я несколько отстал в вопросе стат анализа GitHub Actions и прочей инфраструктуры.
Расскажу о своих находках.
pre-commit ci
Все знают про пакет pre-commit? Несколько лет назад он получил еще и свой собственный CI, который умеет запускаться без дополнительного конфига. И автоматически пушить вам в ветку любые изменения. Что супер удобно для всяких
Строить CI на базе
- Автоматически исправляются многие проблемы
- Автоматически запускается CI, 0 настроек
- Локально все тоже работает одной командой:
actionlint
Первый раз я увидел
Даже умеет автоматом shellcheck запускать на ваши
zizmor
Исходники. Уже на #rust, он более злой. Делает похожие вещи: находит проблемы безопасности. Находит много проблем.
Вот пример, сколько всего он нашел в mypy.
check-jsonschema
Еще есть вот такой проект, он в основном полезен за счет доп интеграций: можно проверять
Ставится просто как:
Выводы
Как всегда – статический анализ многому меня научил. Я узнал много нового про безопасность GitHub Actions, про вектора атаки, про лучшие практики. А сколько проблем в ваших actions?
Скоро ждите весь новый тулинг в python шаблоне
Сразу после релиза новой версии линтера, я задался вопросом обновления своего шаблона для создания новых питоновских библиотек: https://github.com/wemake-services/wemake-python-package
И я понял, что я несколько отстал в вопросе стат анализа GitHub Actions и прочей инфраструктуры.
Расскажу о своих находках.
pre-commit ci
Все знают про пакет pre-commit? Несколько лет назад он получил еще и свой собственный CI, который умеет запускаться без дополнительного конфига. И автоматически пушить вам в ветку любые изменения. Что супер удобно для всяких
ruff / black / isort и прочего. У нас такое стоит в большом количестве проектов. Вот пример из typeshed. Вот что поменялось автоматически. Строить CI на базе
pre-commit очень удобно, потому что тебе просто нужно скопировать пару строк в конфиг. А плюсов много:- Автоматически исправляются многие проблемы
- Автоматически запускается CI, 0 настроек
- Локально все тоже работает одной командой:
pre-commit run TASK_ID -aactionlint
Первый раз я увидел
actionlint внутри CPython и затащил его в mypy. Actionlint на #go, он предлагает набор проверок для ваших GitHub Actions от безопасности до валидации спеки вашего yml. Довольно полезно, позволяет найти много мест для улучшений.
test.yaml:3:5: unexpected key "branch" for "push" section. expected one of "branches", ..., "workflows" [syntax-check]
|
3 | branch: main
| ^~~~~~~
test.yaml:10:28: label "linux-latest" is unknown. available labels are "macos-latest", ..., "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file [runner-label]
|
10 | os: [macos-latest, linux-latest]
| ^~~~~~~~~~~~~
test.yaml:13:41: "github.event.head_commit.message" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions for more details [expression]
|
13 | - run: echo "Checking commit '${{ github.event.head_commit.message }}'"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Даже умеет автоматом shellcheck запускать на ваши
run: скрипты!zizmor
Исходники. Уже на #rust, он более злой. Делает похожие вещи: находит проблемы безопасности. Находит много проблем.
Вот пример, сколько всего он нашел в mypy.
warning[artipacked]: credential persistence through GitHub Actions artifacts
--> mypy/.github/workflows/mypy_primer.yml:37:9
|
37 | - uses: actions/checkout@v4
| _________-
38 | | with:
39 | | path: mypy_to_test
40 | | fetch-depth: 0
| |________________________- does not set persist-credentials: false
|
= note: audit confidence → Low
error[dangerous-triggers]: use of fundamentally insecure workflow trigger
--> mypy/.github/workflows/mypy_primer_comment.yml:3:1
|
3 | / on:
4 | | workflow_run:
... |
7 | | types:
8 | | - completed
| |_________________^ workflow_run is almost always used insecurely
|
= note: audit confidence → Medium
check-jsonschema
Еще есть вот такой проект, он в основном полезен за счет доп интеграций: можно проверять
dependabot.yml, renovate.yml, readthedocs.yml и многое другое.Ставится просто как:
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.30.0
hooks:
- id: check-dependabot
- id: check-github-workflows
Выводы
Как всегда – статический анализ многому меня научил. Я узнал много нового про безопасность GitHub Actions, про вектора атаки, про лучшие практики. А сколько проблем в ваших actions?
Скоро ждите весь новый тулинг в python шаблоне
v2025 😎GitHub
GitHub - wemake-services/wemake-python-package: Bleeding edge cookiecutter template to create new python packages
Bleeding edge cookiecutter template to create new python packages - wemake-services/wemake-python-package
👍66❤16🤯7🔥5👎3😱1
LaranaJS – Рендерим фронтенд в картинку! 🌚
LaranaJS – это большой эксперимент по поиску альтернативных способов рисовать графические интерфейсы. Если большинство других фреймворков полагаются на такие устаревшие технологии как HTML и CSS и вендорлочат себя на браузеры, то Larana делает всё иначе.
Вот как устроены сетевые взаимодействия в LaranaJS.
Браузер запрашивает страницу
На этом этапе происходят создание сессии, резолв роута и инициализация страницы. В то же время разные подсистемы (рендерер, менеджер сессий, роутер и т. д.) генерируют клиентский код — он минимальный – просто canvas и немного работы с сетью по вебсокетам:
Клиент открывает соединение
При полной загрузке страницы создаётся подключение по веб-сокетам и начинается обмен сообщениями:
- Сервер отрисовывает UI в виде изображения (png) и отправляет его на клиент.
- Клиент принимает изображение и вставляет его в canvas.
Все дальнейшие взаимодействия происходят в виде обмена событиями – действия пользователя отправляются клиентом, команды и изображения отправляются сервером.
Такая архитектура позволяет сократить размер клиента до
Несмотря на новизну подхода, сама разработка интерфейсов остаётся привычной. Например, вот код страницы с типичным каунтером:
Большинство задач можно решить с помощью готовых компонентов. Но если есть необходимость сделать что-то необычное, то есть прямой доступ к созданию команд для рендеринга в компоненте
Специально для этого поста я подготовил новогоднее демо:
- Репозиторий: https://github.com/laranatech/snowflakes-demo
- Потыкать: https://snowflakes.larana.tech
Подсистемы
Выше я упоминал подсистемы вроде рендерера и менеджера сессий. Они уже вшиты в фреймворк и можно использовать готовые. Можно написать и собстенные, а потом просто добавить их в приложение:
В ближайших релизах планируется именно развитие подсистем, чтобы сделать фреймворк максимально гибким. Например, сейчас ограничен список событий, которые отслеживаются на клиенте.
Больше подобного авангарда в канале @laranatech!
Автор: @e_kucheriavyi
LaranaJS – это большой эксперимент по поиску альтернативных способов рисовать графические интерфейсы. Если большинство других фреймворков полагаются на такие устаревшие технологии как HTML и CSS и вендорлочат себя на браузеры, то Larana делает всё иначе.
Вот как устроены сетевые взаимодействия в LaranaJS.
Браузер запрашивает страницу
На этом этапе происходят создание сессии, резолв роута и инициализация страницы. В то же время разные подсистемы (рендерер, менеджер сессий, роутер и т. д.) генерируют клиентский код — он минимальный – просто canvas и немного работы с сетью по вебсокетам:
<html>
<!--Minimal head-->
<body>
<canvas id="canvas"></canvas>
<script>
// Network code
</script>
</body>
</html>
Клиент открывает соединение
При полной загрузке страницы создаётся подключение по веб-сокетам и начинается обмен сообщениями:
- Сервер отрисовывает UI в виде изображения (png) и отправляет его на клиент.
- Клиент принимает изображение и вставляет его в canvas.
Все дальнейшие взаимодействия происходят в виде обмена событиями – действия пользователя отправляются клиентом, команды и изображения отправляются сервером.
// event
{
"event": "mousemove",
"x": 0,
"y": 0,
}
// response
{
"image": "", // изображение в base64
"x": 0, // координаты для вставки изображения
"y": 0,
"w": 0,
"h": 0,
}
Такая архитектура позволяет сократить размер клиента до
6KB #js и запускаться в любом браузере c 2009 года. При этом есть возможность написать собственное клиентское приложение и запускать его хоть на esp32 с подключённым дисплеем.Несмотря на новизну подхода, сама разработка интерфейсов остаётся привычной. Например, вот код страницы с типичным каунтером:
class HomePage extends Page {
title() {
return 'Hello, World!'
}
init() {
const { initState } = this.useState()
initState({ counter: 0 })
}
root() {
return layout({
style: 'row',
children: [
button({ text: '+', onClick: () => this.increment() }),
text({ model: 'counter' }),
button({ text: '-', onClick: () => this.decrement() }),
],
})
}
}
Большинство задач можно решить с помощью готовых компонентов. Но если есть необходимость сделать что-то необычное, то есть прямой доступ к созданию команд для рендеринга в компоненте
figure. Например, рисовать сложные фигуры вроде снежинок:
root() {
return figure({
template: (fig, queue) => {
line({
borderColor: '#aaaaff',
borderWidth: 2,
points: [
point({ x: x - halfRadius, y: y - halfRadius }),
point({ x: x + halfRadius, y: y + halfRadius }),
point({ x: x + halfRadius, y: y - halfRadius, moveTo: true }),
point({ x: x - halfRadius, y: y + halfRadius }),
point({ x: x + halfRadius, y, moveTo: true }),
point({ x: x - halfRadius, y }),
point({ x, y: y - halfRadius, moveTo: true }),
point({ x, y: y + halfRadius }),
],
}).to(queue)
},
})
}
Специально для этого поста я подготовил новогоднее демо:
- Репозиторий: https://github.com/laranatech/snowflakes-demo
- Потыкать: https://snowflakes.larana.tech
Подсистемы
Выше я упоминал подсистемы вроде рендерера и менеджера сессий. Они уже вшиты в фреймворк и можно использовать готовые. Можно написать и собстенные, а потом просто добавить их в приложение:
const app = new LaranaApp({
config,
renderer: new ClientRenderer({}),
sessionManager: new MemorySessionManager({}),
router: new DefaultRouter({ routes }),
})
app.run()
В ближайших релизах планируется именно развитие подсистем, чтобы сделать фреймворк максимально гибким. Например, сейчас ограничен список событий, которые отслеживаются на клиенте.
Больше подобного авангарда в канале @laranatech!
Автор: @e_kucheriavyi
GitHub
GitHub - laranatech/snowflakes-demo
Contribute to laranatech/snowflakes-demo development by creating an account on GitHub.
3❤52💩32🤔23🔥8🤯8👍5🤡3👏2🤩2
Лучший курс по Python 12: tuple
https://youtube.com/watch?v=P5OY3Y4Fc7k
Я решил окончательно упороться: сделал видео про
- В чем разница между tuple и list?
- Аннотации tuple
- Тип произведение
- TypeVarTuple, PEP646, Unpack
Для мидлов:
- ast.Tuple
- tuple_iterator
- collections.abc
- collections.namedtuple
- typing.NamedTuple
Для сениоров:
- PyTupleObject
- PyVarObject
- tp_alloc, tp_dealloc, freelists
-
-
- Мутабельность tuple
- PyTuple_Pack, Py_BuildValue
- Виртуальная машина и компилятор: BUILD_TUPLE
- INTRINSIC_LIST_TO_TUPLE
- Оптимизации компилятора
- PySequenceTuple
Обещанный бонус
В видео я обещал, что расскажу в тг, что такое
Документация и исходники: https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/cpython/object.h#L431-L507
По факту - данные два макроса представляют собой
По сути, мы просто при достижении определенного "большого" значения (50) перестаем выполнять деаллокацию напрямую, просто добавляем объекты в список для деаллокации на потом. Вот и вся хитрость!
Завершение
Если вам нравится мой технический контент – его всегда можно поддержать:
- Материально
- Морально: поделиться с вашими коллегами, чтобы они тоже знали все про кортежи :)
#lkpp
| Поддержать | YouTube | GitHub | Чат |
https://youtube.com/watch?v=P5OY3Y4Fc7k
Я решил окончательно упороться: сделал видео про
tuple на 1ч 30м. Зато я рассказал про tuple вообще все, что знал сам. Для джунов:- В чем разница между tuple и list?
- Аннотации tuple
- Тип произведение
- TypeVarTuple, PEP646, Unpack
Для мидлов:
- ast.Tuple
- tuple_iterator
- collections.abc
- collections.namedtuple
- typing.NamedTuple
Для сениоров:
- PyTupleObject
- PyVarObject
- tp_alloc, tp_dealloc, freelists
-
__len__-
__hash__- Мутабельность tuple
- PyTuple_Pack, Py_BuildValue
- Виртуальная машина и компилятор: BUILD_TUPLE
- INTRINSIC_LIST_TO_TUPLE
- Оптимизации компилятора
- PySequenceTuple
Обещанный бонус
В видео я обещал, что расскажу в тг, что такое
Py_TRASHCAN_BEGIN и Py_TRASHCAN_END. Документация и исходники: https://github.com/python/cpython/blob/d05140f9f77d7dfc753dd1e5ac3a5962aaa03eff/Include/cpython/object.h#L431-L507
По факту - данные два макроса представляют собой
do/while цикл, который позволяет более удобно управлять сборкой "контейнеров" (tuple, в нашем случае). Каждый объект внутри "контейнера" может тоже быть контейнером. Таким образом про Py_DECREF(op->ob_item[i]) можно начать каскадную деаллокацию объектов внутри. И мы можем столкнуться с переполнением стека вызовов.
#define Py_TRASHCAN_BEGIN(op, dealloc) \
do { \
PyThreadState *tstate = PyThreadState_Get(); \
if (tstate->c_recursion_remaining <= Py_TRASHCAN_HEADROOM && Py_TYPE(op)->tp_dealloc == (destructor)dealloc) { \
_PyTrash_thread_deposit_object(tstate, (PyObject *)op); \
break; \
} \
tstate->c_recursion_remaining--;
/* The body of the deallocator is here. */
#define Py_TRASHCAN_END \
tstate->c_recursion_remaining++; \
if (tstate->delete_later && tstate->c_recursion_remaining > (Py_TRASHCAN_HEADROOM*2)) { \
_PyTrash_thread_destroy_chain(tstate); \
} \
} while (0);
По сути, мы просто при достижении определенного "большого" значения (50) перестаем выполнять деаллокацию напрямую, просто добавляем объекты в список для деаллокации на потом. Вот и вся хитрость!
Завершение
Если вам нравится мой технический контент – его всегда можно поддержать:
- Материально
- Морально: поделиться с вашими коллегами, чтобы они тоже знали все про кортежи :)
#lkpp
| Поддержать | YouTube | GitHub | Чат |
YouTube
Лучший курс по Python 12: tuple
Лучший курс по питону: 12
Или "обзор исходников CPython с CPython core разработчиком".
Тема: tuple
00:00 Вступление
00:53 Junior
01:24 В чем разница между tuple и list?
07:54 Аннотации tuple
11:05 Тип произведение
14:14 TypeVarTuple, PEP646, Unpack
22:29…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: tuple
00:00 Вступление
00:53 Junior
01:24 В чем разница между tuple и list?
07:54 Аннотации tuple
11:05 Тип произведение
14:14 TypeVarTuple, PEP646, Unpack
22:29…
253🔥170👍24❤10😱8
В asyncio добавили возможность смотреть граф вызова корутин
Ждем в python3.14: https://github.com/python/cpython/commit/188598851d5cf475fa57b4ec21c0e88ce9316ff0
Пример:
Выведет:
Как оно работает?
Появилось два новых важных изменений:
- Поле
- Новое свойство у
Нужно, чтобы отрисовывать
Конечно же есть две иплементации. На питоне уже показал, вот так оно на C:
Как использовать?
Конечно же данная фича умеет не только печатать объекты в stdout. Прежде всего – она предоставляет удобное АПИ для различных IDE и дебагеров, которые смогут использовать данную информацию для визуализации: чего вообще у вас там происходит.
Ну и мониторинги, и sentry, и много кто еще получит дополнительную мета-информацию о процессе выполнения кода.
Документация: https://docs.python.org/3.14/library/asyncio-graph.html
Круто?
| Поддержать | YouTube | GitHub | Чат |
Ждем в python3.14: https://github.com/python/cpython/commit/188598851d5cf475fa57b4ec21c0e88ce9316ff0
Пример:
import asyncio
async def test():
asyncio.print_call_graph()
async def main():
async with asyncio.TaskGroup() as g:
g.create_task(test(), name=test.__name__)
asyncio.run(main())
Выведет:
* Task(name='test', id=0x10304eee0)
+ Call stack:
| File '/Users/sobolev/Desktop/cpython2/Lib/asyncio/graph.py', line 278, in print_call_graph()
| File '/Users/sobolev/Desktop/cpython2/ex.py', line 4, in async test()
+ Awaited by:
* Task(name='Task-1', id=0x1034a1e60)
+ Call stack:
| File '/Users/sobolev/Desktop/cpython2/Lib/asyncio/taskgroups.py', line 121, in async TaskGroup._aexit()
| File '/Users/sobolev/Desktop/cpython2/Lib/asyncio/taskgroups.py', line 72, in async TaskGroup.__aexit__()
| File '/Users/sobolev/Desktop/cpython2/ex.py', line 7, in async main()
Как оно работает?
Появилось два новых важных изменений:
- Поле
Frame.f_generator – оно хранит генератор или корутину, которая владеет данным фреймом. Нужно чтобы отрисовывать + Call stack:- Новое свойство у
Future
@property
def _asyncio_awaited_by(self):
if self.__asyncio_awaited_by is None:
return None
return frozenset(self.__asyncio_awaited_by)
Нужно, чтобы отрисовывать
+ Awaited by:.Конечно же есть две иплементации. На питоне уже показал, вот так оно на C:
/*[clinic input]
@critical_section
@getter
_asyncio.Future._asyncio_awaited_by
[clinic start generated code]*/
static PyObject *
_asyncio_Future__asyncio_awaited_by_get_impl(FutureObj *self)
/*[clinic end generated code: output=... input=...]*/
{
/* Implementation of a Python getter. */
if (self->fut_awaited_by == NULL) {
Py_RETURN_NONE;
}
if (self->fut_awaited_by_is_set) {
/* Already a set, just wrap it into a frozen set and return. */
assert(PySet_CheckExact(self->fut_awaited_by));
return PyFrozenSet_New(self->fut_awaited_by);
}
PyObject *set = PyFrozenSet_New(NULL);
if (set == NULL) {
return NULL;
}
if (PySet_Add(set, self->fut_awaited_by)) {
Py_DECREF(set);
return NULL;
}
return set;
}
Как использовать?
Конечно же данная фича умеет не только печатать объекты в stdout. Прежде всего – она предоставляет удобное АПИ для различных IDE и дебагеров, которые смогут использовать данную информацию для визуализации: чего вообще у вас там происходит.
Ну и мониторинги, и sentry, и много кто еще получит дополнительную мета-информацию о процессе выполнения кода.
Документация: https://docs.python.org/3.14/library/asyncio-graph.html
Круто?
| Поддержать | YouTube | GitHub | Чат |
GitHub
GH-91048: Add utils for capturing async call stack for asyncio progra… · python/cpython@1885988
…ms and enable profiling (#124640)
Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
Co-authored-by: Pablo Galindo <pablogsal@gmail.com>
Co-authored-by: Kumar Aditya...
Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
Co-authored-by: Pablo Galindo <pablogsal@gmail.com>
Co-authored-by: Kumar Aditya...
55🔥181👍46❤14🤔1
Enum и сложность
Enum – один из самых сложных модулей в питоне, я не шучу. Количество нюансов – просто огромное. Так как я последние несколько дней занимаюсь улучшением поддержки
Доки: https://docs.python.org/3/library/enum.html Их все равно никто не читает.
global_enum
Чудовищная фича. Просто ужас. Засовывает все значения
Да, создает новые глобальные константы. Нет, mypy такое пока не поддерживает.
Для чего нужно? Синтаксический сахар для обратной совместимости. Когда были раньше
_simple_enum
Специальный внутренний хелпер для более быстрого создания
Декоратор, который позволит найди дубликаты по значениям в ваших
и искать её всю жизнь.
Flag и FlagBoundary
Зачем нужны
Для сочетания друг с другом:
Советую всегда использовать
Черная дыра для багов и изменений от версии к версии.
member и nonmember
В Enum есть довольно сложная логика, какие объекты считать за member (часть enum), какие за nonmember (просто какие-то объекты).
Кратко:
- Имя не приватное, имя не
- Имя не в
- Не вложенный класс и не метод
- Не инстанс
Пример:
Думаю, что всем спалось спокойнее без такого знания. Старайтесь делать такие енамы, чтобы не приходилось использовать темную магию. И старайтесь не пользоваться
Enum в .pyi файлах
Недавно Typing Spec для енамов был изменен. https://typing.readthedocs.io/en/latest/spec/enums.html
Раньше в
Однако, теперь такой способ будет создавать два nonmember'а. Правильный способ:
Почему? Потому что значения полей – крайне важно для типа Enum.
Для старого кода mypy выкидывает ошибку.
Обсуждение: а вам нравились
| Поддержать | YouTube | GitHub | Чат |
Enum – один из самых сложных модулей в питоне, я не шучу. Количество нюансов – просто огромное. Так как я последние несколько дней занимаюсь улучшением поддержки
Enum в mypy, то я решил рассказать про интересные штуки из модуля enum, которые вы скорее всего могли пропустить.Доки: https://docs.python.org/3/library/enum.html Их все равно никто не читает.
global_enum
Чудовищная фича. Просто ужас. Засовывает все значения
Enum в sys.modules[module].__dict__:
>>> from enum import Enum, global_enum
>>> @global_enum
... class Pets(Enum):
... CAT = 1
... DOG = 2
>>> print(CAT)
CAT
Да, создает новые глобальные константы. Нет, mypy такое пока не поддерживает.
Для чего нужно? Синтаксический сахар для обратной совместимости. Когда были раньше
CAT и DOG как константы в модуле, а потом появляется Enum. Но все равно не советую._simple_enum
>>> from enum import IntEnum, _simple_enum
>>>
>>> @_simple_enum(IntEnum)
... class Pets:
... CAT = 1
... DOG = 2
...
>>> print(Pets.CAT)
1
Специальный внутренний хелпер для более быстрого создания
Enum классов. Используется в основном внутри CPython для ускорения импорта библиотек. Не поддерживается mypy.@uniqueДекоратор, который позволит найди дубликаты по значениям в ваших
Enum типах и вызвать ошибку. Обязателен для использования. Иначе, где-то можно сделать опечатку:
@unique # <- will find the problem
class Pets(Enum):
CAT = 1
DOG = 1 # should be 2
и искать её всю жизнь.
Flag и FlagBoundary
Зачем нужны
Flag?
from enum import IntFlag, FlagBoundary
class Permission(IntFlag, boundary=FlagBoundary.STRICT):
READ = 0
WRITE = 1
Для сочетания друг с другом:
Permission.WRITE | Permission.READ. Есть 4 разных поведения для таких случаев: https://docs.python.org/3/library/enum.html#enum.FlagBoundaryСоветую всегда использовать
FlagBoundary.STRICT, если сомневаетесь.Черная дыра для багов и изменений от версии к версии.
member и nonmember
В Enum есть довольно сложная логика, какие объекты считать за member (часть enum), какие за nonmember (просто какие-то объекты).
Кратко:
- Имя не приватное, имя не
__dunder__ и имя не _sunder_- Имя не в
_ignore_- Не вложенный класс и не метод
- Не инстанс
nonmemberПример:
from enum import Enum, member, nonmember
class Example(Enum):
_ignore_ = ['a'] # nonmember
a = 1 # nonmember
b = 2 # member
__c__ = 3 # nonmember
_d_ = 4 # nonmember
e = nonmember(5) # nonmember
__f = 6 # nonmember
def g(self): ... # nonmember
@member
def h(self): ... # member
Думаю, что всем спалось спокойнее без такого знания. Старайтесь делать такие енамы, чтобы не приходилось использовать темную магию. И старайтесь не пользоваться
member и nonmember.Enum в .pyi файлах
Недавно Typing Spec для енамов был изменен. https://typing.readthedocs.io/en/latest/spec/enums.html
Раньше в
.pyi файлах мы аннотировали енамы так:
# mymodule.pyi
class Pets(Enum):
CAT: int
DOG: int
Однако, теперь такой способ будет создавать два nonmember'а. Правильный способ:
# mymodule.pyi
class Pets(Enum):
CAT = 1
DOG = 2
Почему? Потому что значения полей – крайне важно для типа Enum.
Для старого кода mypy выкидывает ошибку.
Обсуждение: а вам нравились
Enum в Python? А сейчас?| Поддержать | YouTube | GitHub | Чат |
GitHub
Properly account for `member` and `nonmember` in `TypeInfo.enum_members` by sobolevn · Pull Request #18559 · python/mypy
Closes #18557
756🔥109👍58🤯36❤14🕊3
Ковыряем внутрянку nogil
Некоторое время назад я прислал безобидный PR, который исправлял поведение
Во-первых, я случайно удалил оптимизацию.
Было:
Стало:
В питоне есть аналогичная оптимизация. Использовать
более оптимально, чем
если у вас много объектов в
Во-вторых, когда мне указали на ошибку, я понял, что я так до конца и не понял разницу между
А вот тут потребуется пояснительная бригада. Смотрите, при использовании nogil, у нас теперь несколько потоков, который выполняют сишный код (который генерирует нужный ASM). И там есть своя специфика. Начнем с того, что есть специальное понятие – Memory Ordering. Базово – как и в каком порядке будут идти обращения от CPU к памяти. Что становится критически важно, когда у нас появляется multi-threading.
Базово, у нас может быть несколько видов memory ordering:
-
-
Полный референс.
Как можно увидеть: в первом случае происходит непосредственное вычисление адреса
Полный пример на godbolt. Тема для меня новая, продолжаю изучать.
Обсуждение: а вы думали, что nogil – оно просто?
| Поддержать | YouTube | GitHub | Чат |
Некоторое время назад я прислал безобидный PR, который исправлял поведение
list.insert в nogil сборках CPython. Изменений на 3 строчки. И в ревью случилось два интересных момента.Во-первых, я случайно удалил оптимизацию.
Было:
PyObject **items;
items = self->ob_item;
items[i+1] = items[i];
items[where] = Py_NewRef(v);
0x0000000000132860 <+96>: sub rdx,0x1
0x0000000000132864 <+100>: sub rax,0x8
0x0000000000132868 <+104>: mov rcx,QWORD PTR [rax]
0x000000000013286b <+107>: mov QWORD PTR [rax+0x8],rcx
0x000000000013286f <+111>: cmp rsi,rdx
0x0000000000132872 <+114>: jle 0x132860 <ins1+96>
Стало:
self->ob_item[i+1] = self->ob_item[i];
self->ob_item[where] = Py_NewRef(v);
0x0000000000132858 <+88>: mov rdx,QWORD PTR [r12+0x28]
0x000000000013285d <+93>: lea rcx,[rax*8+0x0]
0x0000000000132865 <+101>: mov rdi,QWORD PTR [rdx+rax*8]
0x0000000000132869 <+105>: sub rax,0x1
0x000000000013286d <+109>: mov QWORD PTR [rdx+rcx*1+0x8],rdi
0x0000000000132872 <+114>: cmp rsi,rax
0x0000000000132875 <+117>: jle 0x132858 <ins1+88>
В питоне есть аналогичная оптимизация. Использовать
items = self.items
for _ in whatever:
some_func(items)
более оптимально, чем
for _ in whatever:
some_func(self.items)
если у вас много объектов в
whatever.
LOAD_NAME 3 (self)
LOAD_ATTR 8 (items) # <- won't happen when `items = self.items`
CALL 1
Во-вторых, когда мне указали на ошибку, я понял, что я так до конца и не понял разницу между
FT_ATOMIC_STORE_PTR_RELAXED и FT_ATOMIC_STORE_PTR_RELEASE.А вот тут потребуется пояснительная бригада. Смотрите, при использовании nogil, у нас теперь несколько потоков, который выполняют сишный код (который генерирует нужный ASM). И там есть своя специфика. Начнем с того, что есть специальное понятие – Memory Ordering. Базово – как и в каком порядке будут идти обращения от CPU к памяти. Что становится критически важно, когда у нас появляется multi-threading.
Базово, у нас может быть несколько видов memory ordering:
-
memory_order_relaxed – Relaxed operation: there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed-
memory_order_release – A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomicПолный референс.
__atomic_store_n(&x->ob_item[0], &first, __ATOMIC_RELEASE) будет скомпилировано в
lea rdx, [rsp+12]
mov QWORD PTR [rax], rdx
__atomic_store_n(&x->ob_item[0], &first, __ATOMIC_RELAXED) скомпилируется в
mov rax, QWORD PTR [rbx]
mov QWORD PTR [rax], rdx
Как можно увидеть: в первом случае происходит непосредственное вычисление адреса
rsp+12 инструкцией lea. А во втором случае – мы просто работаем со значением в регистре rbx.Полный пример на godbolt. Тема для меня новая, продолжаю изучать.
Обсуждение: а вы думали, что nogil – оно просто?
| Поддержать | YouTube | GitHub | Чат |
GitHub
gh-129643: Fix `PyList_Insert` in free-threading builds by sobolevn · Pull Request #129680 · python/cpython
Addressing @colesbury's comment here: #129643 (comment)
Issue: PyList_SetItem missing atomic store #129643
Issue: PyList_SetItem missing atomic store #129643
🤯93👍34🔥12❤5
--strict-bytes в mypy@1.15Вообще, внутри mypy есть много всякой дичи, которую нельзя выразить системой типов нормально. И потому разные хаки просто приколачивают гвоздями. Например,
int и float связывают псевдо-"наследованием", чтобы штуки вроде 1 + 1.0 == 2.0 работали нормально.Раньше так было и с
bytes / bytearray / memoryview. То есть буквально можно было писать:
def func(arg: bytes) -> None:
assert isinstance(arg, bytes)
func(b'') # ok
func(bytearray(b'123')) # type checks, fails in runtime
func(memoryview(b'abc')) # type checks, fails in runtime
Даже со всеми
--strict флагами. Были спрятанные --disable-bytearray-promotion и --disable-memoryview-promotion, но кто же про них знал?PEP688
Почему было так? Потому что до PEP688 у нас не было возможности выразить C'шный тип
Buffer, который появился недавно. И его выражали сначала как просто bytes (да, bytes был синонимом readonly-buffer долгое время), а потом стали выражать как:
ReadOnlyBuffer: TypeAlias = bytes
WriteableBuffer: TypeAlias = bytearray | memoryview | array.array[Any] | mmap.mmap | ctypes._CData | pickle.PickleBuffer
ReadableBuffer: TypeAlias = ReadOnlyBuffer | WriteableBuffer
Теперь все можно выразить при помощи collections.abc.Buffer.
Следовательно, нам больше не нужны type-promote для
bytes / bytearray / memoryview. И флаг --strict-bytes убирает такое приведение из mypy:
# --strict-bytes
def func(arg: bytes) -> None:
assert isinstance(arg, bytes)
func(b'') # ok
func(bytearray(b'123')) # Argument 1 to "func" has incompatible type "bytearray"; expected "bytes"
func(memoryview(b'abc')) # Argument 1 to "func" has incompatible type "memoryview[int]"; expected "bytes"
Советую начинать использовать как можно раньше, потому что такое поведение будет включено в
--strict с mypy@2.0, так же как и --local-partial-types. Лучше подготовиться заранее.Два поста за два дня!
Обсуждение: как вы аннотируете объекты, которые принимают
Buffer у себя в коде?| Поддержать | YouTube | GitHub | Чат |
GitHub
mypy/mypy/semanal_classprop.py at c8fad3f6a97eda2f5a0fa3a581db1194976998b8 · python/mypy
Optional static typing for Python. Contribute to python/mypy development by creating an account on GitHub.
👍49🔥11❤7🤔2
Лучший курс по Python 13: print
https://www.youtube.com/watch?v=9aQ-GVlC0nY
В рамках данного видео я рассказываю про:
- Файловые дескрипторы
- Буферизацию вывода
- Устройство TextIOWrapper, BufferedWrite, FileIO
- Зачем нам _pyio?
- Что такое syscall
- Что происходит после вызова syscall на запись
Для лучшего закрепления материала я предлагаю вам поучаствовать в переписывании
Много задач! Поменять формат ASM, дописать пару ключевых вещей, возможно добавить поддержку дополнительных операционных систем и архитектур.
Данная задача реально поможет разобраться с
Ах да, совсем забыл:
Тут
Еще писали так:
Где
И вот так:
Тогда компилятор уже начинал использовать
Если вам было полезно и интересно, не забывайте поддерживать:
- Поделиться с коллегами
- Закинуть на бусти: https://boosty.to/sobolevn
| Поддержать | YouTube | GitHub | Чат |
https://www.youtube.com/watch?v=9aQ-GVlC0nY
В рамках данного видео я рассказываю про:
- Файловые дескрипторы
- Буферизацию вывода
- Устройство TextIOWrapper, BufferedWrite, FileIO
- Зачем нам _pyio?
- Что такое syscall
write- Что происходит после вызова syscall на запись
Для лучшего закрепления материала я предлагаю вам поучаствовать в переписывании
print на ASM. Внутри:
static long
sys_write_call(const char *msg, Py_ssize_t size)
{
// TODO: allow to pass `fd` as `print(file=...)` does.
long ret;
asm volatile (
// TODO: convert this ugly AT&T ASM into beautiful Intel one:
"mov $1, %%rax\n" // sys_write call number
"mov $1, %%rdi\n" // stdout=1 and stderr=2
"mov %1, %%rsi\n" // `msg` address
"mov %2, %%rdx\n" // `msg_len`
"syscall\n"
"mov %%rax, %0\n" // save the result
: "=r"(ret)
: "r"(msg), "r"(size) // inputs
: "rax", "rdi", "rsi", "rdx" // changed registers
);
// TODO: maybe handle special cases like `EINTR`
return ret;
}
Много задач! Поменять формат ASM, дописать пару ключевых вещей, возможно добавить поддержку дополнительных операционных систем и архитектур.
Данная задача реально поможет разобраться с
print в CPython на самом низком уровне. Мне было очень интересно! Надеюсь, и вам будет.Ах да, совсем забыл:
print в Python2 был ключевым словом, а не функцией. Нам приходилось писать так:
print 'Hello, world!'
Тут
print - ключевое слово, а 'Hello, world!' – объект класса bytes. Еще писали так:
print(1, 2)
Где
print - все еще ключевое слово, а (1, 2) - tuple.И вот так:
from __future__ import print_function
print(1, 2)
Тогда компилятор уже начинал использовать
print как функцию. Ужас!Если вам было полезно и интересно, не забывайте поддерживать:
- Поделиться с коллегами
- Закинуть на бусти: https://boosty.to/sobolevn
| Поддержать | YouTube | GitHub | Чат |
YouTube
Лучший курс по Python 13: print
Лучший курс по питону: 13
Или "обзор исходников CPython с CPython core разработчиком".
Тема: print
00:00 Вступление
00:44 Junior
02:45 Тип print
03:55 sys.stdout
04:46 open syscall и файловые дескрипторы
06:38 echo
07:42 PYTHONENCODING
08:53 Буферизация…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: print
00:00 Вступление
00:44 Junior
02:45 Тип print
03:55 sys.stdout
04:46 open syscall и файловые дескрипторы
06:38 echo
07:42 PYTHONENCODING
08:53 Буферизация…
60🔥99❤28👍13🤯3
Что такое GIL в Python?
Кажется, один из золотых вопросов для всех питонистов на собеседованиях.
Обычно, на встречный вопрос "а что конкретно в питоне является GIL?" не может ответить ни один спрашивающий.
Сегодня мы закроем данный пробел в знаниях питонистов.
Global Interpreter Lock не позволяет более чем одному треду работать с Python API за раз. Его можно отключить через
Обратите внимание на ключевую фразу "c Python API". С системными треды могут и должны работать в режиме настоящей параллельности, без GIL. Что и позволяет получить ускорение при использовании
Знакомьтесь – вот структура GIL
Как можно отпустить GIL?
На уровне C есть макросы: Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS, которые отпускают GIL в нужных местах. Пример из модуля mmap:
Или time.sleep, который тоже дает работать другим тредам, пока ждет.
Что происходит, когда мы используем данный макрос? Они разворачиваются в:
Когда вызывается PyEval_SaveThread и GIL отпускается, то на самом деле мы просто помечаем текущий PyThreadState как:
И вызываем _PyEval_ReleaseLock, который уже правильно изменит
Как итог – текущий стейт теряет возможность вызывать какие-либо Python АПИ. Даже, например
Как треды берут GIL?
Смотрим на thread_run из
Там используется PyEval_AcquireThread, который берет GIL в конкретном треде для работы с Python API.
И дальше – отпускаем.
В следующих сериях поговорим про переключение тредов, ParkingLot API, Mutex'ы и прочее.
Обсуждение: сталкивались ли вы на собесах с вопросами про GIL? Стало ли теперь понятнее?
| Поддержать | YouTube | GitHub | Чат |
Кажется, один из золотых вопросов для всех питонистов на собеседованиях.
Обычно, на встречный вопрос "а что конкретно в питоне является GIL?" не может ответить ни один спрашивающий.
Сегодня мы закроем данный пробел в знаниях питонистов.
Global Interpreter Lock не позволяет более чем одному треду работать с Python API за раз. Его можно отключить через
--disable-gil в 3.13+, но сегодня мы про такое не будем.Обратите внимание на ключевую фразу "c Python API". С системными треды могут и должны работать в режиме настоящей параллельности, без GIL. Что и позволяет получить ускорение при использовании
threading, когда C код поддерживает такой способ.Знакомьтесь – вот структура GIL
_gil_runtime_state и поведение в ceval_gil.c.Как можно отпустить GIL?
На уровне C есть макросы: Py_BEGIN_ALLOW_THREADS и Py_END_ALLOW_THREADS, которые отпускают GIL в нужных местах. Пример из модуля mmap:
Py_BEGIN_ALLOW_THREADS
m_obj->data = mmap(NULL, map_size, prot, flags, fd, offset);
Py_END_ALLOW_THREADS
Или time.sleep, который тоже дает работать другим тредам, пока ждет.
Что происходит, когда мы используем данный макрос? Они разворачиваются в:
{
PyThreadState *_save;
_save = PyEval_SaveThread();
// your code here
PyEval_RestoreThread(_save);
}
PyThreadState является текущим состоянием треда в CPython. Внутри хранится много контекста. Нас особо сильно интересует часть с полями про GIL:
struct PyThreadState {
struct {
unsigned int initialized:1;
/* Has been bound to an OS thread. */
unsigned int bound:1;
/* Has been unbound from its OS thread. */
unsigned int unbound:1;
/* Has been bound aa current for the GILState API. */
unsigned int bound_gilstate:1;
/* Currently in use (maybe holds the GIL). */
unsigned int active:1;
/* Currently holds the GIL. */
unsigned int holds_gil:1;
} _status;
// Thread state (_Py_THREAD_ATTACHED, _Py_THREAD_DETACHED, _Py_THREAD_SUSPENDED).
int state;
// ...
}
Когда вызывается PyEval_SaveThread и GIL отпускается, то на самом деле мы просто помечаем текущий PyThreadState как:
tstate->_status.active = 0;
tstate->_status.unbound = 1;
tstate->_status.holds_gil = 0;
tstate->state = detached_state;
И вызываем _PyEval_ReleaseLock, который уже правильно изменит
_gil_runtime_state. Как итог – текущий стейт теряет возможность вызывать какие-либо Python АПИ. Даже, например
Py_DECREF, и в тредах есть свой refcount, который работает локально, чтобы можно было его вызывать без GIL.Как треды берут GIL?
Смотрим на thread_run из
_threadmodule.c.
_PyThreadState_Bind(tstate);
PyEval_AcquireThread(tstate);
_Py_atomic_add_ssize(&tstate->interp->threads.count, 1);
Там используется PyEval_AcquireThread, который берет GIL в конкретном треде для работы с Python API.
И дальше – отпускаем.
В следующих сериях поговорим про переключение тредов, ParkingLot API, Mutex'ы и прочее.
Обсуждение: сталкивались ли вы на собесах с вопросами про GIL? Стало ли теперь понятнее?
| Поддержать | YouTube | GitHub | Чат |
GitHub
cpython/Include/internal/pycore_gil.h at fb2d325725dcc881868b576b9d0d9f4bf7f24fe0 · python/cpython
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
63👍93🔥39❤16🤯8👌2🥰1🤡1
Что такое GIL в Python? Вторая часть
Я не закончил! 🌚
Мы не поговорили про очень важную часть: переключение тредов интерпретатором. Треды могут быть очень долгими и не отпускать GIL, мы должны дать поработать каждому.
Простой пример, что так оно и работает:
Выдаст что-то вроде:
Как VM CPython переключает потоки?
Важно: у CPython нет своего scheduler'а для тредов. Он целиком полагается на OS в данном вопросе. Однако, GIL дает возможность переодически останавливать работу с Python API одного потока и дать время какому-то другому. Какому – решает уже OS. Но как?
Во-первых, у нас есть замечательный sys.setswitchinterval и sys.getswitchinterval, которые отвечают за примерное время работы одного потока, которое хранится в
Примерный вывод:
Далее: значение
Когда флаг
После вызова
Часть про VM
Теперь вопрос, а кто вызывает
Вызывается оно из специального "псевдо-опкода"
Вот теперь мы знаем, как переключаются треды внутри CPython.
Одной строкой
- Замечательная (и более длинная) статья Андрея Светлова на русском про GIL, которую не вчера скинули в нашем чате (реклама)
- Предложение по отмене
Если интересно – закидывайте в коллег!
| Поддержать | YouTube | GitHub | Чат |
Я не закончил! 🌚
Мы не поговорили про очень важную часть: переключение тредов интерпретатором. Треды могут быть очень долгими и не отпускать GIL, мы должны дать поработать каждому.
Простой пример, что так оно и работает:
import threading
def first():
while True:
print('first')
def two():
while True:
print('two')
a = threading.Thread(target=first).start()
b = threading.Thread(target=two).start()
Выдаст что-то вроде:
first
two
first
first
two
two
two
first
first
Как VM CPython переключает потоки?
Важно: у CPython нет своего scheduler'а для тредов. Он целиком полагается на OS в данном вопросе. Однако, GIL дает возможность переодически останавливать работу с Python API одного потока и дать время какому-то другому. Какому – решает уже OS. Но как?
Во-первых, у нас есть замечательный sys.setswitchinterval и sys.getswitchinterval, которые отвечают за примерное время работы одного потока, которое хранится в
_gil_runtime_state:
unsigned long _PyEval_GetSwitchInterval(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
struct _gil_runtime_state *gil = interp->ceval.gil;
assert(gil != NULL);
return gil->interval;
}
Примерный вывод:
>>> import sys
>>> sys.getswitchinterval()
0.005
Далее: значение
gil->interval будет использовано для вызова pthread_cond_timedwait. Мы ждем interval для передачи события (чтобы нам дали GIL) через gil->cond. Если уходим в таймаут, значит пришло время забирать GIL на следующем выполнении байткода силой. Смотри про сигналы и PyCOND_T тут.
MUTEX_LOCK(gil->mutex);
unsigned long interval = (gil->interval >= 1 ? gil->interval : 1);
int timed_out = 0;
COND_TIMED_WAIT(gil->cond, gil->mutex, interval, timed_out);
/* If we timed out and no switch occurred in the meantime, it is time
to ask the GIL-holding thread to drop it. */
if (timed_out && _Py_atomic_load_int_relaxed(&gil->locked)) {
PyThreadState *holder_tstate =
(PyThreadState*)_Py_atomic_load_ptr_relaxed(&gil->last_holder);
assert(_PyThreadState_CheckConsistency(tstate));
_Py_set_eval_breaker_bit(holder_tstate, _PY_GIL_DROP_REQUEST_BIT);
}
Когда флаг
_PY_GIL_DROP_REQUEST_BIT будет установлен, мы сможем в _Py_HandlePending передать GIL кому-то другому:
/* GIL drop request */
if ((breaker & _PY_GIL_DROP_REQUEST_BIT) != 0) {
/* Give another thread a chance */
_PyThreadState_Detach(tstate);
/* Other threads may run now */
_PyThreadState_Attach(tstate);
}
После вызова
_PyThreadState_Detach(tstate) текущий тред потеряет GIL. И снова будет ждать его при вызове _PyThreadState_Attach(tstate). Пока другой работает.Часть про VM
Теперь вопрос, а кто вызывает
_Py_HandlePending и когда?Вызывается оно из специального "псевдо-опкода"
_CHECK_PERIODIC. Раньше там был макрос CHECK_EVAL_BREAKER, и его иногда забывали добавить в нужные места. Оттого события ОС не обрабатывались, GIL не переключался, было весело.
op(_CHECK_PERIODIC, (--)) {
_Py_CHECK_EMSCRIPTEN_SIGNALS_PERIODICALLY();
QSBR_QUIESCENT_STATE(tstate);
if (_Py_atomic_load_uintptr_relaxed(&tstate->eval_breaker) & _PY_EVAL_EVENTS_MASK) {
int err = _Py_HandlePending(tstate);
ERROR_IF(err != 0, error);
}
}
// Вызываем `_CHECK_PERIODIC` в конце каждого `CALL`.
macro(CALL) =
_SPECIALIZE_CALL
+ unused/2
+ _MAYBE_EXPAND_METHOD
+ _DO_CALL
+ _CHECK_PERIODIC;
Вот теперь мы знаем, как переключаются треды внутри CPython.
Одной строкой
- Замечательная (и более длинная) статья Андрея Светлова на русском про GIL, которую не вчера скинули в нашем чате (реклама)
- Предложение по отмене
PyGILState АПИ, потому что оно не работает нормально с субинтерпретаторами Если интересно – закидывайте в коллег!
| Поддержать | YouTube | GitHub | Чат |
Python documentation
sys — System-specific parameters and functions
This module provides access to some variables used or maintained by the interpreter and to functions that interact strongly with the interpreter. It is always available. Unless explicitly noted oth...
56👍72❤23🔥5👏1🤯1🤡1
Находки в опенсорсе: mypy@2.0
Так как чуваки на бусти собрали цель в 50 человек, я сделал видео, которое обещал.
https://www.youtube.com/watch?v=vrOwcOKIIf4
Теперь "Находки в опенсорсе" еще и в видео формате!
Рассказываю, что будет в новом релизе: что сломаем, что добавим.
Пока релиз планируется где-то на вторую половину года, а я уже про него рассказываю.
Если понравится формат – поддержи видео, покажи коллеге :)
| Поддержать | YouTube | GitHub | Чат |
Так как чуваки на бусти собрали цель в 50 человек, я сделал видео, которое обещал.
https://www.youtube.com/watch?v=vrOwcOKIIf4
Теперь "Находки в опенсорсе" еще и в видео формате!
Рассказываю, что будет в новом релизе: что сломаем, что добавим.
Пока релиз планируется где-то на вторую половину года, а я уже про него рассказываю.
Если понравится формат – поддержи видео, покажи коллеге :)
| Поддержать | YouTube | GitHub | Чат |
boosty.to
sobolevn - OpenSource & Education & Настольные игры
Привет! Я делаю множество OpenSource проектов: https://github.com/sobolevn В их числе: CPython, mypy, typeshed, dry-python, TypedDjango, проекты внутри wemake-services (wemake-python-styleguide и другие), hypothesis, git-secret и еще много всякого!…
6👍83❤28🔥4🤯2🤡1
Находки в опенсорсе pinned «Находки в опенсорсе: mypy@2.0 Так как чуваки на бусти собрали цель в 50 человек, я сделал видео, которое обещал. https://www.youtube.com/watch?v=vrOwcOKIIf4 Теперь "Находки в опенсорсе" еще и в видео формате! Рассказываю, что будет в новом релизе: что сломаем…»
Media is too big
VIEW IN TELEGRAM
zen browser
После недавней оказии с FireFox, я понял, что нужно менять свой браузер.
Выбор пал на zen (почти arc, но для firefox), потому что я люблю минимализм.
Что мне нужно от браузера?
- Несколько вкладок, у меня их никогда не бывает сильно много, я все их закрываю примерно раз в день
- Панель для ввода адреса с минимумом функциональности (подсказки, история, поиск)
- Минималистичный интерфейс, без лишних кнопок
- Поддержка uBlock, нескольких других похожих плагинов
- Приватность по-умолчанию
Все. Остальные фичи мне скорее мешают. Я не пользуюсь закладками, workspacе'ами, профилями, синками и тд.
Что есть в zen?
Во-первых, браузер почти полностью позволяет убрать свой интерфейс, что приятно. Теперь по пунктам:
- Hidden Tabs: можно настроить "compact mode", чтобы вкладки исчезали, когда они не нужны, нажатие
- Floating Nav Bar: После настройки панель навигации сверху исчезает, когда ей не пользуешься (открывается на
- Busy Mode: при нажатие
- Tab Preview: отключаемая фича, которая позволяет сделать превью страницы и быстро ее закрыть, выглядит полезно для поиска
- Split View: отключаемая фича, которая позволяет открывать две вкладки слева и справа (у меня на `alt-v`) или сверху и снизу (`alt-h`), выглядит полезно для ревью PRов на гитхабе
Ну и конечно же работают все плагины для FireFox и даже есть свои уникальные.
Сверху я все шлифанул кастомным CSS для уничтожения некоторых объектов UI, которые меня отвлекали.
Пока пробую – и мне нравится.
Обсуждение: что сейчас еще есть интересного и удобного в мире браузеров?
| Поддержать | YouTube | GitHub | Чат |
После недавней оказии с FireFox, я понял, что нужно менять свой браузер.
Выбор пал на zen (почти arc, но для firefox), потому что я люблю минимализм.
Что мне нужно от браузера?
- Несколько вкладок, у меня их никогда не бывает сильно много, я все их закрываю примерно раз в день
- Панель для ввода адреса с минимумом функциональности (подсказки, история, поиск)
- Минималистичный интерфейс, без лишних кнопок
- Поддержка uBlock, нескольких других похожих плагинов
- Приватность по-умолчанию
Все. Остальные фичи мне скорее мешают. Я не пользуюсь закладками, workspacе'ами, профилями, синками и тд.
Что есть в zen?
Во-первых, браузер почти полностью позволяет убрать свой интерфейс, что приятно. Теперь по пунктам:
- Hidden Tabs: можно настроить "compact mode", чтобы вкладки исчезали, когда они не нужны, нажатие
cmd+b показывает вкладки, нажатие cmd+1 открывает первую вкладку и тд- Floating Nav Bar: После настройки панель навигации сверху исчезает, когда ей не пользуешься (открывается на
cmd+t для открытия новой вкладки и cmd+L фокуса в текущей)- Busy Mode: при нажатие
ctrl+b включает интерфейс, если нужно что-то найти, если идет какой-то напряженный рабочий режим- Tab Preview: отключаемая фича, которая позволяет сделать превью страницы и быстро ее закрыть, выглядит полезно для поиска
- Split View: отключаемая фича, которая позволяет открывать две вкладки слева и справа (у меня на `alt-v`) или сверху и снизу (`alt-h`), выглядит полезно для ревью PRов на гитхабе
Ну и конечно же работают все плагины для FireFox и даже есть свои уникальные.
Сверху я все шлифанул кастомным CSS для уничтожения некоторых объектов UI, которые меня отвлекали.
Пока пробую – и мне нравится.
Обсуждение: что сейчас еще есть интересного и удобного в мире браузеров?
| Поддержать | YouTube | GitHub | Чат |
👍91🔥15❤6🤡3👎2🥰1🤔1
Находки в опенсорсе
Как работает диспатчеризация байткода внутри VM? Computed GOTOs Многие из вас знают, что внутри питона есть большой switch-case, который выполняется в цикле, он находит нужный байткод и выполняет его. Выглядит оно примерно как-то так: #define LOAD_CONST…
Как работает диспатчеризация байткода внутри VM? Tail call dispatch
(перед прочтением – советую прочитать пост ^ про computed goto)
https://github.com/python/cpython/pull/128718
В CPython новая оптимизация, которая дает где-то 5% производительности. Я уже рассказывал, что такое computed goto, но теперь есть еще более прикольная и быстрая штука для диспатчеризации байткода.
То есть: вызов следующего опкода в Python коде будет быстрее, а значит – все программы просто бесплатно станут быстрее.
(не путать с tail call оптимизацией для рекурсии)
Как работает?
Сначала делаем два макроса, которые будут устанавливать нужные атрибуты для компилятора.
Пока только [[clang::musttail]], про поддержку компиляторов будет ниже. Зачем нужен preserve_none – можно прочитать тут.
Далее, создаем новый тип колбеков для "tail-call функций":
Важный шаг: меняем дефиницию макросов
Теперь тут будет:
То есть теперь по факту – все
В теле такой функции будет очень мало кода – только обработка ее логики. Пример для BINARY_OP.
Вот они, для каждого опкода:
И мы так же ищем следующий опкод в
И во время конфигурации сборки питона – проверяем, поддерживает ли наш компилятор такое.
Так почему быстрее?
Теперь – все функции маленькие, их удобно оптимизировать. Вот тут уточнение из комментов.
Потому что для
Для вызова функции
Стало:
Статья по теме от автора
Ограничения
Пока что данное поведение скрыто за флагом
Есть еще и техническое ограничение. Пока что такой
Ну и последнее: пока проверили только перформанс с Profile Guided Optimization (pgo), сколько будет без него – еще не мерили. Сначала вообще заявили прирост на 15%, но потом нашли баг в llvm, который замедлял код без такой фичи.
Да, у нас тут с вами душный канал, где нет ярких заголовков :(
Обсуждение: чего ждете от 3.14 больше всего?
| Поддержать | YouTube | GitHub | Чат |
(перед прочтением – советую прочитать пост ^ про computed goto)
https://github.com/python/cpython/pull/128718
В CPython новая оптимизация, которая дает где-то 5% производительности. Я уже рассказывал, что такое computed goto, но теперь есть еще более прикольная и быстрая штука для диспатчеризации байткода.
То есть: вызов следующего опкода в Python коде будет быстрее, а значит – все программы просто бесплатно станут быстрее.
(не путать с tail call оптимизацией для рекурсии)
Как работает?
Сначала делаем два макроса, которые будут устанавливать нужные атрибуты для компилятора.
Пока только [[clang::musttail]], про поддержку компиляторов будет ниже. Зачем нужен preserve_none – можно прочитать тут.
#ifdef Py_TAIL_CALL_INTERP
// Note: [[clang::musttail]] works for GCC 15, but not __attribute__((musttail)) at the moment.
# define Py_MUSTTAIL [[clang::musttail]]
# define Py_PRESERVE_NONE_CC __attribute__((preserve_none))
// Для простоты еще два макроса, просто слишком часто повторяется код:
#define TAIL_CALL_PARAMS _PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate, _Py_CODEUNIT *next_instr, int oparg
#define TAIL_CALL_ARGS frame, stack_pointer, tstate, next_instr, oparg
Далее, создаем новый тип колбеков для "tail-call функций":
Py_PRESERVE_NONE_CC typedef PyObject* (*py_tail_call_funcptr)(TAIL_CALL_PARAMS);
Важный шаг: меняем дефиницию макросов
TARGET и DISPATCH_GOTO по аналогии с computed gotos.Теперь тут будет:
# define TARGET(op) Py_PRESERVE_NONE_CC PyObject *_TAIL_CALL_##op(TAIL_CALL_PARAMS)
# define DISPATCH_GOTO() \
do { \
Py_MUSTTAIL return (INSTRUCTION_TABLE[opcode])(TAIL_CALL_ARGS); \
} while (0)
То есть теперь по факту – все
TARGET макросы будут разворачиваться в отдельные функции:
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_BINARY_OP(TAIL_CALL_PARAMS);
В теле такой функции будет очень мало кода – только обработка ее логики. Пример для BINARY_OP.
Вот они, для каждого опкода:
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_BINARY_OP(TAIL_CALL_PARAMS);
Py_PRESERVE_NONE_CC static PyObject *_TAIL_CALL_LIST_APPEND(TAIL_CALL_PARAMS);
// ...
И мы так же ищем следующий опкод в
INSTRUCTION_TABLE[opcode], но теперь мы вызываем функцию, которая там лежит в DISPATCH_GOTO. То есть теперь – у нас теперь есть буквально:
callbacks = {
'BINARY_OP': lambda *args, **kwargs: ...
'LIST_APPEND': lambda *args, **kwargs: ...
}
callbacks[opcode](*args, **kwargs)
И во время конфигурации сборки питона – проверяем, поддерживает ли наш компилятор такое.
Так почему быстрее?
Теперь – все функции маленькие, их удобно оптимизировать. Вот тут уточнение из комментов.
Потому что для
[[mustail]] не создается дополнительный стекфрейм, asm получается более оптимальным. Я подготовил для вас пример: https://godbolt.org/z/T3Eqnd33e (для таких простых случаев -O2 более чем работает, но все равно)Для вызова функции
foo(int a) было:
mov edi, dword ptr [rbp - 4]
call foo(int)@PLT
add rsp, 16
pop rbp
ret
Стало:
mov edi, dword ptr [rbp - 4]
pop rbp
jmp foo(int)@PLT
call -> jmp!Статья по теме от автора
__attribute__((musttail))Ограничения
Пока что данное поведение скрыто за флагом
--with-tail-call-interp, по-умолчанию в 3.14 оно работать не будет. В следующих версиях – включат по-умолчанию для всех.Есть еще и техническое ограничение. Пока что такой
__attribute__ компилятора поддерживает только clang в llvm>=19 на x86-64 и AArch64. В следующем релизе gcc, вроде бы, завезут поддержкуНу и последнее: пока проверили только перформанс с Profile Guided Optimization (pgo), сколько будет без него – еще не мерили. Сначала вообще заявили прирост на 15%, но потом нашли баг в llvm, который замедлял код без такой фичи.
Да, у нас тут с вами душный канал, где нет ярких заголовков :(
Обсуждение: чего ждете от 3.14 больше всего?
| Поддержать | YouTube | GitHub | Чат |
53👍62🤯8❤5🤡2🔥1👏1
Forwarded from Находки в опенсорсе: Python (Никита Соболев)
Привет! Стартуем новый проект для любителей опенсорса: помогаем меинтейнерам и контрибьюторам найти друг друга.
Как оно работает?
- В данном канале меинтейнеры разных Python проектов (от CPython, mypy, Litestar до taskiq) могут в любой момент выложить простые задачки, чтобы люди могли принять участие в разработке их проекта
- Если вы хотите поработать над задачкой – напишите в самой задаче на гитхабе: "Can I work on this?", получите подтверждение меинтейнера и приступайте
- Делитесь успехами / задавайте вопросы в нашем чате @opensource_findings_chat
Если вы меинтейнер какого-то крупного проекта (>= 100 ⭐), то пишите в чат – вас добавят как админа, чтобы вы смогли постить в канал свои задачи. Чем больше – тем лучше, не забывайте ставить тег своей технологии.
Всем хорошего опенсорса!
Как оно работает?
- В данном канале меинтейнеры разных Python проектов (от CPython, mypy, Litestar до taskiq) могут в любой момент выложить простые задачки, чтобы люди могли принять участие в разработке их проекта
- Если вы хотите поработать над задачкой – напишите в самой задаче на гитхабе: "Can I work on this?", получите подтверждение меинтейнера и приступайте
- Делитесь успехами / задавайте вопросы в нашем чате @opensource_findings_chat
Если вы меинтейнер какого-то крупного проекта (>= 100 ⭐), то пишите в чат – вас добавят как админа, чтобы вы смогли постить в канал свои задачи. Чем больше – тем лучше, не забывайте ставить тег своей технологии.
Всем хорошего опенсорса!
17👍93❤22🔥22😱3🤯1🤡1
Лучший курс по Python 14: Steering Council
https://www.youtube.com/watch?v=KKgsaTtezW0
Пригласил Donghee Na, одного из 5 членов Steering Council – ключевого органа управления разработкой CPython – рассказать о своей работе.
Мне кажется, что разработчикам – важно понимать, как развиваются инструменты, которыми они пользуются. Как принимаются технические решения, как происходит обсуждение. И что в таких решения можно и нужно участвовать!
Затронули крайне важные темы:
- Free-threading
- JIT
- Tail-call dispatch и faster cpython
Donghee оставил свои контакты, если кто-то хочет серьезно начать работу над free-threading.
А я получил большое удовольствие от нашего общения. Надеюсь, что вы тоже оцените.
Советую смотреть интервью с субтитрами: есть на русском 🇷🇺 и на английском 🇺🇸.
Обсуждение: если у вас есть идеи, кого из интересных гостей пригласить – пишите в чат!
| Поддержать | YouTube | GitHub | Чат |
https://www.youtube.com/watch?v=KKgsaTtezW0
Пригласил Donghee Na, одного из 5 членов Steering Council – ключевого органа управления разработкой CPython – рассказать о своей работе.
Мне кажется, что разработчикам – важно понимать, как развиваются инструменты, которыми они пользуются. Как принимаются технические решения, как происходит обсуждение. И что в таких решения можно и нужно участвовать!
Затронули крайне важные темы:
- Free-threading
- JIT
- Tail-call dispatch и faster cpython
Donghee оставил свои контакты, если кто-то хочет серьезно начать работу над free-threading.
А я получил большое удовольствие от нашего общения. Надеюсь, что вы тоже оцените.
Советую смотреть интервью с субтитрами: есть на русском 🇷🇺 и на английском 🇺🇸.
Обсуждение: если у вас есть идеи, кого из интересных гостей пригласить – пишите в чат!
| Поддержать | YouTube | GitHub | Чат |
YouTube
Лучший курс по Python 14: Steering Council
Лучший курс по питону: 14
Или "обзор исходников CPython с CPython core разработчиком".
Тема: Steering Council
Контакты гостя:
- https://github.com/corona10
- https://twitter.com/dongheena92
- donghee.na@python.org
00:00 Вступление
02:07 Представление гостя…
Или "обзор исходников CPython с CPython core разработчиком".
Тема: Steering Council
Контакты гостя:
- https://github.com/corona10
- https://twitter.com/dongheena92
- donghee.na@python.org
00:00 Вступление
02:07 Представление гостя…
83👍59🔥32❤18🤯2🤡2
mlut - новое слово в подходе Atomic CSS
mlut (читается как млат) - это инструмент для верстки в подходе Atomic #css, с которым можно создавать стили любой сложности. Что-то похожее на Tailwind, но по некоторым параметрам превосходит все популярные аналоги.
Atomic CSS - это методология верстки, в которой мы используем маленькие атомарные классы, каждый из которых делает одно действие. Эти классы называют утилитами. Обычно они применяет одно CSS-свойство (например, цвет текста), но не обязательно одно. Выглядит в коде это примерно так:
Преимущества такого подхода
1. Тратим меньше мыслетоплива: не думаем о нейминге сущностей, структуре каталогов
2. Меньше CSS на клиенте: реиспользуем одни и те же утилиты, а новые стили почти перестают добавляться
3. Быстрее пишем стили: короткие классы, нет переключения файлов
4. Можно применять на любом стеке: JS SPA, Ruby, Clojure, etc
Возвращаясь к mlut, вот чем он отличается от конкурентов
Строгий нейминг
Tailwind:
-
-
-
mlut:
-
-
-
Для всех сокращений используется единый алгоритм. Сокращения Emmet не имеют четких правил составления (подтвердил создатель), поэтому они не подошли.
Важность такого алгоритма еще и в том, что он помогает запоминать сокращения. Можно разобраться в нем однажды и выводить большую часть аббревиатур в голове, а не зазубривать их. Если пару раз мысленно развернуть одно сокращение, то оно быстро запомнится до автоматизма.
Почти произвольные значения by design
- значения утилит:
- states:
- at-rules:
Хорошая эргономика
Более короткие className:
Удобный синтаксис для сложных утилит
Tailwindcss:
-
-
-
mlut:
-
-
-
Удобно расширять
Утилиты, states и кастомные at-rules добавляются парой строк кода
Чем еще может похвастаться mlut
- Написан на Sass, все плюсы которого идут в комплекте
- Доступен как JIT (on demand), так и AOT режим работы
- Отличная кастомизация
Состояние проекта
Что реализовано на сегодня:
- Генератор утилит почти любой сложности
- JIT-движок, который умеет генерировать утилиты из HTML/JSX/etc
- CLI с минификацией и автопрефиксером
- Плагины для сборщиков фронтенда: Webpack, Vite и Rollup
- Документация
- Интерактивный мини-курс по инструменту совместно с HTML Academy
- Больше технических деталей есть в расшифровке доклада с HolyJS.
Автор будет благодарен любому фидбеку и особенно звездам на гитхабе!
mlut (читается как млат) - это инструмент для верстки в подходе Atomic #css, с которым можно создавать стили любой сложности. Что-то похожее на Tailwind, но по некоторым параметрам превосходит все популярные аналоги.
Atomic CSS - это методология верстки, в которой мы используем маленькие атомарные классы, каждый из которых делает одно действие. Эти классы называют утилитами. Обычно они применяет одно CSS-свойство (например, цвет текста), но не обязательно одно. Выглядит в коде это примерно так:
<button class="D-ib P1r Bgc-blue_h">
Submit
</button>
.D-ib {
display: inline-block;
}
.P1r {
padding: 1rem;
}
.Bgc-blue_h:hover {
background-color: blue;
}
Преимущества такого подхода
1. Тратим меньше мыслетоплива: не думаем о нейминге сущностей, структуре каталогов
2. Меньше CSS на клиенте: реиспользуем одни и те же утилиты, а новые стили почти перестают добавляться
3. Быстрее пишем стили: короткие классы, нет переключения файлов
4. Можно применять на любом стеке: JS SPA, Ruby, Clojure, etc
Возвращаясь к mlut, вот чем он отличается от конкурентов
Строгий нейминг
Tailwind:
-
flex => display: flex, но flex-auto => flex: 1 1 auto-
tracking-wide => letter-spacing: 0.025em-
justify-* => content, items, self?mlut:
-
Js-c => justify-self: center-
Bdr => border-right: 1px solid-
Bdrd1 => border-radius: 1pxДля всех сокращений используется единый алгоритм. Сокращения Emmet не имеют четких правил составления (подтвердил создатель), поэтому они не подошли.
Важность такого алгоритма еще и в том, что он помогает запоминать сокращения. Можно разобраться в нем однажды и выводить большую часть аббревиатур в голове, а не зазубривать их. Если пару раз мысленно развернуть одно сокращение, то оно быстро запомнится до автоматизма.
Почти произвольные значения by design
- значения утилит:
Ml-1/7 =>
margin-left: -14.3%
- states:
Bgc-red200_h,f =>
.Bgc-red200_h\,f:hover,
.Bgc-red200_h\,f:focus {
/* ... */
}
- at-rules:
@:p-c,w>=80r_D-f =>
@media (pointer: coarse), (min-width: 80rem) {
/* ... */
}
Хорошая эргономика
Более короткие className:
<!-- Tailwind -->
<div class="relative -bottom-px col-span-full col-start-1 row-start-2 h-px bg-(--cardBg)"></div>
<!-- mlut -->
<div class="Ps B-1 Gc1/-1 Gcs1 Grs2 H1 Bgc-$cardBg"></div>
Удобный синтаксис для сложных утилит
Tailwindcss:
-
[@media(any-hover:hover){&:hover}]:opacity-100-
text-[length:var(--myVar,1.3rem)]-
supports-[margin:1svw]:ml-[1svw]mlut:
-
@:ah_O1_h =>
@media (any-hover) {
.\@\:ah_O1_h:hover {
opacity: 1
}
}
-
Fns-$myVar?1.3 =>
font-size: var(--ml-myVar, 1.3rem);
-
@s_Ml1svw =>
@supports (margin-left: 1svw) {
.\@s_Ml1svw {
margin-left: 1svw
}
}
Удобно расширять
Утилиты, states и кастомные at-rules добавляются парой строк кода
@use 'mlut' with (
$utils-data: (
'utils': (
'registry': (
'Mil': margin-inline,
),
),
),
);
@include mlut.apply('Mil-13');
// CSS
.Mil-13 {
margin-inline: -13px;
}
Чем еще может похвастаться mlut
- Написан на Sass, все плюсы которого идут в комплекте
- Доступен как JIT (on demand), так и AOT режим работы
- Отличная кастомизация
Состояние проекта
Что реализовано на сегодня:
- Генератор утилит почти любой сложности
- JIT-движок, который умеет генерировать утилиты из HTML/JSX/etc
- CLI с минификацией и автопрефиксером
- Плагины для сборщиков фронтенда: Webpack, Vite и Rollup
- Документация
- Интерактивный мини-курс по инструменту совместно с HTML Academy
- Больше технических деталей есть в расшифровке доклада с HolyJS.
Автор будет благодарен любому фидбеку и особенно звездам на гитхабе!
GitHub
GitHub - mlutcss/mlut: Make CSS exciting again!
Make CSS exciting again! Contribute to mlutcss/mlut development by creating an account on GitHub.
🤮52🔥33💩15👍8🤡8🤯5❤3😱3
Находки в опенсорсе: taskiq
https://www.youtube.com/watch?v=HcZ2FAy_srM
Сегодня в опенсорсе я нашел современную замену Celery с асинхронностью и тайпхинтами.
Поговорили с автором про то, как устроена библиотека внутри:
- Как сделать универсальные интерфейсы для всех видов очередей
- Что Redis – вообще-то не очередь, и не стоит его использовать для таких задач
- Как устроена асинхронность библиотеки внутри
- Как запускать задачи по расписанию
- Как делать сложные canvas для задач с несколькими шагами
- Обсудили модульность: каждая реализация живет в отдельном пакете
Библиотека выглядит солидно! 🌚
Видео получилось коротким, но максимально информативным.
Мне нравится такой формат, буду приглашать больше авторов разных прикольных штук и обсуждать с ними устройство их технологий.
Если ваши коллеги все еще хотят делать celery таски с
Обсуждение: используете ли вы celery все еще в своих проектах? Если нет, то на что перешли?
| Поддержать | YouTube | GitHub | Чат |
https://www.youtube.com/watch?v=HcZ2FAy_srM
Сегодня в опенсорсе я нашел современную замену Celery с асинхронностью и тайпхинтами.
Поговорили с автором про то, как устроена библиотека внутри:
- Как сделать универсальные интерфейсы для всех видов очередей
- Что Redis – вообще-то не очередь, и не стоит его использовать для таких задач
- Как устроена асинхронность библиотеки внутри
- Как запускать задачи по расписанию
- Как делать сложные canvas для задач с несколькими шагами
- Обсудили модульность: каждая реализация живет в отдельном пакете
Библиотека выглядит солидно! 🌚
Видео получилось коротким, но максимально информативным.
Мне нравится такой формат, буду приглашать больше авторов разных прикольных штук и обсуждать с ними устройство их технологий.
Если ваши коллеги все еще хотят делать celery таски с
asyncio.run внутри – срочно покажите им видос и уберегите от греха!Обсуждение: используете ли вы celery все еще в своих проектах? Если нет, то на что перешли?
| Поддержать | YouTube | GitHub | Чат |
YouTube
Находки в опенсорсе: taskiq
taskiq – современная замена celery
Ссылки на taskiq:
- Группа в тг: https://xn--r1a.website/taskiq_py
- Сервер в discord: https://discord.gg/8hPcuHAb
- Документация: https://taskiq-python.github.io
- Исходники: https://github.com/taskiq-python
00:00 Вступление
01:08…
Ссылки на taskiq:
- Группа в тг: https://xn--r1a.website/taskiq_py
- Сервер в discord: https://discord.gg/8hPcuHAb
- Документация: https://taskiq-python.github.io
- Исходники: https://github.com/taskiq-python
00:00 Вступление
01:08…
18👍108🔥30❤23👎3🤡1
wemake-python-styleguide@1.1.0
Вышла новая версия самого строго линтера для питона. Теперь еще строже!
Главная фича релиза:
А так же несколько новых правил:
-
-
Ну и много разных багов поправили, куда без них.
Полный список изменений: https://github.com/wemake-services/wemake-python-styleguide/releases/tag/1.1.0
Большое спасибо участникам нашего чата за PRы, они затащили релиз 🧡
Обсуждение: каких правил в wemake-python-styleguide вам не хватает? Какие душат вас сильнее всего? Что можно улучшить?
| Поддержать | YouTube | GitHub | Чат |
Вышла новая версия самого строго линтера для питона. Теперь еще строже!
Главная фича релиза:
wps explain CLI, которая позволяет видеть вывод информации: почему что-то запрещено, и как такое исправить.А так же несколько новых правил:
-
WPS476 не дает использовать await в for (потому что вы скорее всего хотите использовать asyncio.gather, чтобы добиться асинхронности)-
WPS477 запрещает сложную комбинацию TypeVarTuple рядом с TypeVar с дефолтным значением: class Class[T=int, *Ts=*tuple[int, ...]]:Ну и много разных багов поправили, куда без них.
Полный список изменений: https://github.com/wemake-services/wemake-python-styleguide/releases/tag/1.1.0
Большое спасибо участникам нашего чата за PRы, они затащили релиз 🧡
Обсуждение: каких правил в wemake-python-styleguide вам не хватает? Какие душат вас сильнее всего? Что можно улучшить?
| Поддержать | YouTube | GitHub | Чат |
5👍69❤20🔥20🤡6🤔1
PEP765: больше никакой грязи в finally
Ссылка на PEP: https://peps.python.org/pep-0765
Одна из самых сломанных частей питона была пофикшена в 3.14
В чем проблема?
Ранее такой код вел себя крайне странно:
Так как из функции может быть только один
Или такой код:
Тут вообще жесть. У нас подавляется исключение без
Аналогично можно делать не только с
Такой код позволял создавать ошибки на ровном месте.
Как исправили?
Теперь такой код генерирует
Скоро – будет
Но, WPS, например, запрещал такое делать уже очень давно.
Как работает?
Сам патч очень маленький и простой:
1. Добавляет список текущего контекста
Храним внутри структуры вида:
2. При обходе дерева, добавляет нужные данные в текущий контекст: находимся ли мы в
3. Если находим
4. Внутри
Готово!
Обсуждение: вы знали про такую проблему? Стреляли так себе в ногу?
P.S. Пока писал пост, нашел багу в питоне и важную опечатку :)
| Поддержать | YouTube | GitHub | Чат |
Ссылка на PEP: https://peps.python.org/pep-0765
Одна из самых сломанных частей питона была пофикшена в 3.14
В чем проблема?
Ранее такой код вел себя крайне странно:
>>> def some():
... try:
... return 1
... finally:
... return 2
>>> some()
2
Так как из функции может быть только один
return, а код в finally всегда выполняется – то получаем, что получаем.Или такой код:
>>> def other():
... try:
... 1 / 0
... finally:
... return 2
>>> other()
2
Тут вообще жесть. У нас подавляется исключение без
except 🫠Аналогично можно делать не только с
return, но и с break / continue в циклах:
>>> def cycle():
... for i in range(2):
... try:
... i / 0
... finally:
... print(i)
... continue
... return 2
>>> cycle()
# prints: 0
# prints: 1
# returns: 2
Такой код позволял создавать ошибки на ровном месте.
Как исправили?
Теперь такой код генерирует
SyntaxWarning в 3.14:
>>> def some():
... try:
... return 1
... finally:
... return 2
<python-input-14>:5: SyntaxWarning: 'return' in a 'finally' block
Скоро – будет
SyntaxError, в будущих версиях.Но, WPS, например, запрещал такое делать уже очень давно.
Как работает?
Сам патч очень маленький и простой:
1. Добавляет список текущего контекста
_Py_c_array_t cf_finally; в ast_opt.cХраним внутри структуры вида:
typedef struct {
bool in_finally; // мы в `finally`?
bool in_funcdef; // мы в `def` или `async def`?
bool in_loop; // мы в `for`, `async for` или `while`?
} ControlFlowInFinallyContext;
2. При обходе дерева, добавляет нужные данные в текущий контекст: находимся ли мы в
finally, функции, цикле3. Если находим
return, break или continue, то выполняем проверку синтаксиса; вот код для return:
static int
before_return(_PyASTOptimizeState *state, stmt_ty node_)
{
if (state->cf_finally_used > 0) {
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
// если нашли `return` в `finally`, но не во вложенной функции,
// то показываем warning пользователю:
if (ctx->in_finally && ! ctx->in_funcdef) {
if (!control_flow_in_finally_warning("return", node_, state)) {
return 0;
}
}
}
return 1;
}
4. Внутри
control_flow_in_finally_warning используем специальное АПИ для SyntaxWarning:
static int
control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTOptimizeState *state)
{
PyObject *msg = PyUnicode_FromFormat("'%s' in a 'finally' block", kw);
if (msg == NULL) {
return 0;
}
int ret = _PyErr_EmitSyntaxWarning(msg, state->filename, n->lineno,
n->col_offset + 1, n->end_lineno,
n->end_col_offset + 1);
Py_DECREF(msg);
return ret < 0 ? 0 : 1;
}
Готово!
Обсуждение: вы знали про такую проблему? Стреляли так себе в ногу?
P.S. Пока писал пост, нашел багу в питоне и важную опечатку :)
| Поддержать | YouTube | GitHub | Чат |
11👍142❤37🔥27🤡7🤯5🎉2👏1😁1