Comments 9
Прикольно. Для трэйсинга прям серебрянная пуля похоже.
Кстати, согласен. Полезная фича, хоть и спрятанная и неочевидная.
Во фреймворке Kopf сначала выполняется код фреймворка, потом пользовательские функции или обработчики, и потом опять код фреймворка для некоторых функций, вызываемых пользователями из обработчиков.
Совершенно бесполезно и бессмысленно просить пользователей передавать какие-либо аргументы "насквозь", даже если это будет один объект-контекст: всё равно забудут; да и будет некрасиво.
И тут контекстные переменные приходят на помощь: во фреймворке перед входом в пользовательские код устанавливаем значения, а в вызываемых пользователями функциях восстанавливаем значения. И так эти значения проходят "сквозь" пользовательский код, не требуя никаких действий со стороны пользователя.
Для примера, передача текущего обрабатываемого случая/объекта:
- Устанавливаем: https://github.com/nolar/kopf/blob/0.28/kopf/reactor/handling.py#L362
- Вызываем: https://github.com/nolar/kopf/blob/0.28/examples/02-children/example.py#L28
- Восстаналиваем: https://github.com/nolar/kopf/blob/0.28/kopf/toolkits/hierarchies.py#L150
Или подобное — вызов вторичных обработчиков из главного обработчика, когда нужно знать хотя бы идентификатор главного:
- Устанавливаем: https://github.com/nolar/kopf/blob/0.28/kopf/reactor/handling.py#L361
- Вызываем: https://github.com/nolar/kopf/blob/0.28/docs/idempotence.rst (где
kopf.execute()
) - Восстанавливам: https://github.com/nolar/kopf/blob/0.28/kopf/reactor/handling.py#L91
Но есть нюансы, конечно. Нельзя вызвать одну задачу (например, login()
), которая заполнит эти переменные, а потом другую, которая их использует (например, doit()
). Значения переменных уходят вглубь стека и в порождённые задачи, но никак не в родительские или в одноуровневые родственные задачи. Это местами немного раздражает, так как приходится делать обёртку вокруг этих login-doit, которая сама и хранит состояние.
Но, несмотря на полезность для некоторых видов задач, эти контекстные переменные — просто хитрый вид глобальных переменных. Глобальные переменные чреваты тем, что в коде сложно проследить откуда приходят значения переменных — если они приходят не через аргументы функций по стеку вызовов. И эти контекстные переменные открывают портал в ад (в плане отладки). Вопрос лишь в том, когда (не "если") в проекте появится тот человек, который начнёт ими злоупотреблять.
Совершенно бесполезно и бессмысленно просить пользователей передавать какие-либо аргументы "насквозь", даже если это будет один объект-контекст: всё равно забудут; да и будет некрасиво.
А почему?
Почему забудут? Ну, почему бы и не забыть?
Почему некрасиво? Потому что в Python-based DSL их предметной области — K8s-оператор чего-нибудь — вносится сущность, которая не даёт им никакой пользы и выгоды, и не относится к предметной области, которой они оперируют, ни даже к предметной области K8s-операторов как таковых. Leaked abstraction во плоти.
И, кстати, потому и забудут: сущность, не относящаяся к предметной области, — первый кандидат на забывание. Чем больше правил, требований и соглашений вводится, тем хуже они соблюдаются (фраза верна в любом контексте с людьми).
contextvars — это не фича, которую нужно брать в каждый проект. Однако она способна сделать код значительно проще и чище, если правильно проектировать архитектуру сервиса.
Вы о чём?
В PEP чётко сказано — если у вас используется async и вам нужен threading.local(), то вам надо вместо него использовать ContextVar!
О какой полезности идёт речь? Это конкретный инструмент для работы с асинхронным кодом.
В python 3.10 часть assert'ов уже не выполняется.
О полезности contextvars