ctx python что такое
ctx 0.1.2
pip install ctx Copy PIP instructions
Released: Dec 19, 2014
A minimal but opinionated dict/object combo (like Bunch).
Navigation
Project links
Statistics
View statistics for this project via Libraries.io, or by using our public dataset on Google BigQuery
License: MIT License (MIT)
Maintainers
Classifiers
Project description
Requirements
The ctx module should work with all versions of Python.
Features
The ctx module provides the Ctx class which is a subclass of the python ‘dict’ object.
Ctx modifies ‘dict’ in the following ways:
1 The dictionary items can be read or set using attribute access notation.
‘ctx.a’ is identical to ‘ctx[“a”]’ and ‘ctx.a = 5’ is identical to ‘ctx[“a”] = 5’
2 The objects attributes can be read using item access notation.
‘ctx[“__doc__”]’ is identical to ‘ctx.__doc__’
3 The objects attributes can not be set under any circumstances.
4 The dictionary can not have a key with the same name as an objects attribute.
‘ctx.name’ and ‘ctx[“name”]’ are resolved using the following three steps.
b) if the dictionary has a key ‘name’ then return the value associated with the key.
‘ctx.name = 5’ and ‘ctx[“name”] = 5’
Installation
You can install this package using pip with the following command.
Support
To report any bugs, or ask any question, please visit
Resources
Here is a list of useful links about this project.
Тестируем асинхронный код с помощью PyTest (перевод)
При подготовке материала для курса, нам периодически попадаются интересные статьи, которыми хотелось бы поделиться с вами!
PyTest — отличный пакет для тестирования на Python, и с давних пор один из моих любимых пакетов в целом. Он значительно облегчает написание тестов и обладает широкими возможностями по составлению отчетов о непройденных тестах.
Тем не менее, на момент версии 2.7, он менее эффективен в тестировании (asyncio) подпрограмм. Поэтому не стоит пытаться их тестировать таким способом:
В таком методе много недостатков и излишеств. Единственные интересные строки — те, что содержат операторы yield from и assert.
Каждый тесткейс должен иметь свой событийный цикл, который завершается корректно вне зависимости от успешности прохождения теста.
Применить yield вышеописанным способом не получится, pytest решит что наш тест возвращает новые тесткейсы.
Поэтому необходимо создать отдельную подпрограмму, в которой содержится фактический тест, а для его выполнения запускается событийный цикл.
Тесты будут чище, если сделать вот так:
Pytest обладает гибкой системой плагинов, благодаря чему имплементация такого поведения возможна. Но, к сожалению, большая часть нужных хуков задокументирована плохо или совсем никак, поэтому узнавать способы их выполнения проблематично.
Локальный попапочный плагин создаётся просто потому что это чуть проще, чем создавать “настоящий” внешний плагин. Pytest находит в каждой тестовой директории файл с названием conftest.py и применяет фикстуры и хуки, имплементированные в нём, ко всем тестам этой директории.
Начнём с написания фикстуры, которая создаёт новый событийный цикл для каждого тесткейса и закрывает его корректно по окончании теста:
Перед каждым тестом pytest выполняет фикстуру loop до первого оператора yield. То, что возвращает yield, передаётся в качестве аргумента loop (то есть цикла) нашего тесткейса. Когда тест окончен (успешно или нет), pytest завершает выполнение фикстуры цикла, закрывая его корректно. Таким же образом можно написать тестовую фикстуру, которая создаёт сокет и закрывает его после каждого теста (фикстура сокета может зависеть от фикстуры цикла точно так же, как в нашем примере. Круто, не правда ли?)
Но до конца ещё далеко. Нужно научить pytest выполнять наши тестовые подпрограммы. Для этого нужно изменить, как asyncio подпрограммы собираются (они должны собираться как обычные тестовые функции, а не как тестовые генераторы) и как они выполняются (с помощью loop.run_until_complete()):
Этот плагин работает в pytest версии 2.4 и выше. Я проверял его работоспособность с версиями 2.6 и 2.7.
И всё бы было хорошо, но вскоре после опубликования данного решения в Stack Overflow, появился плагин PyTest-Asyncio, но Стефан нисколько не расстроился, а сделал подробный разбор этого плагина
Продвинутое тестирование асинхронного кода
В своей первой статье я показал, как pytest помогает писать качественные тесты. Фикстуры позволяют создавать чистый событийный цикл для каждого тесткейса, а благодаря системе плагинов можно писать тестовые функции, которые на самом деле являются сопрограммами asyncio.
Но пока велась работа над этим материалом, Тин Твртковиц создал плагин pytest-asyncio.
Если коротко, он позволяет делать так:
Использование pytest-asyncio наглядно улучшает тест (и это не предел возможностей плагина!).
Когда я работал над aiomas, столкнулся с дополнительными требованиями, выполнить которые было не так просто.
Немного о том, что представляет собой сам aiomas. Он добавляет три слоя абстракции asyncio транспортам:
Требования к тестам
Нужен чистый событийный цикл для каждого теста.
Теоретически это можно решить с помощью декоратора pytest.mark.parametrize() (но не в нашем случае, как дальше это станет ясно).
Каждому тесту нужна клиентская сопрограмма. В идеале, сам тест.
Декоратор pytest.mark.asyncio в pytest-asyncio справляется с этой задачей.
Каждому тесту необходим сервер с кастомным колбеком для клиентских соединений. По завершении теста сервера должны быть выключены вне зависимости от результата тестирования.
Кажется, что сопрограмма может решить эту задачу, но каждому серверу необходим конкретный колбек для управления клиентскими соединениями. Что усложняет решение проблемы. Не хочется получать ошибки “Address already in use”, если один из тестов не выполнится. Фикстура unused_tcp_port в pytest-asyncio спешит на помощь.
Не хочется постоянно использовать loop.run_until_complete().
И декоратор pytest.mark.asyncio решает задачу.
Обобщим, что осталось решить: каждому тесту нужно две фикстуры (одна для событийного цикла и еще одна для типа адреса), но я хочу объединить их в одну. Нужно создать фикстуру для настройки сервера, но как это сделать?
Первый подход
Можно обернуть цикл и тип адреса в фикстуру. Назовём её ctx (сокращенно от test context). Благодаря параметрам фикстуры легко создать отдельную на каждый тип адреса.
Это позволяет написать тесты так:
Работает уже неплохо, и каждый тест, использующий фикстуру ctx, запускается единожды для каждого типа адреса.
Тем не менее, остаётся две проблемы:
Второй подход
Первая проблема решается использованием метода getfuncargvalue(), принадлежащего объекту request, который получает наша фикстура. Этим методом можно вручную вызвать её функцию:
Для решения второй проблемы можно расширить класс Context, который передаётся в каждый тест. Добавляем метод Context.start_server(client_handler), который можно вызывать прямо из тестов. А также добавляем завершающий teardown в нашу ctx фикстуру, которая закроет сервер после окончания. Помимо всего прочего, можно создать несколько функций для шорткатов.
Тесткейс становится ощутимо короче, проще для восприятия и надёжней благодаря такому дополнительному функционалу:
Фикстура ctx (и класс Context), конечно, не самая короткая, что я когда-либо писал, но она помогла избавить мои тесты примерно от 200 строчек шаблонного кода.
Реализуем преобразования кода на Python
Сегодня мы предлагаем вам перевод статьи, затрагивающей не самую обсуждаемую тему: компиляцию кода в Python, а именно: работу с абстрактным синтаксическим деревом (AST) и байт-кодом. Притом, что Python является интерпретируемым языком, такие возможности в нем чрезвычайно важны с точки зрения оптимизации. О них мы сегодня и поговорим.
Вы когда-нибудь задумывались, как именно компилятор оптимизирует ваш код, чтобы он работал быстрее? Хотите узнать, что такое абстрактное синтаксическое дерево (AST) и для чего оно может использоваться?
В этой обзорной статье рассказано, как код Python преобразуется в древовидную форму (AST). Соорудив AST вашей программы, вы сможете перейти к поиску возможностей оптимизации и преобразования вашего кода. Однако учтите, что оптимизация программ Python нетривиальными способами исключительно сложна.
Код программы как дерево
Как компьютеру убедиться, что он вычисляет выражения из вашего кода в правильном порядке?
Для этого он сначала переделывает код вашей программы в древовидную структуру под названием AST.
В Python применяются стандартные правила математической нотации (сначала умножение, затем сложение). Чтобы ничего не перепутать с приоритетом операторов, в Python сначала строится именно такое дерево, как на предыдущей картинке. Общая операция – это сложение (у корня дерева), и, тогда как левая часть этой суммы представляет собой обычное число, справа мы имеем произведение. Результирующая структура данных выглядит так:
BinOp означает Binary Operation (Бинарная операция) и указывает на то, что в таких операциях как сложение и умножение – по два операнда. Естественно, никакого сложения у вас не получится, если в правой части выражения не будет правильного значения – поэтому сначала нужно произвести умножение.
Однако вы заметите, что в AST, сгенерированном Python, будут дополнительные узлы и поля, а выводиться оно будет в одну строку, из-за чего на первый взгляд оно кажется сложнее, чем есть на самом деле.
Давайте разобьем его на отдельные узлы, как и в прошлый раз – и заново откроем AST, уже сверху, как часть всего дерева:
Процесс компиляции: оставшаяся часть
Иными словами, процесс компиляции программы, написанной на Python, проходит в два этапа. Сначала программа, полученная на вход, проходит парсинг, и в результате получается абстрактное синтаксическое дерево (AST). Затем компилятор проходит по AST и при этом генерирует байт-код. После чего интерпретатор Python выполняет этот байт-код. Взявшись за оптимизацию, ее можно применить или на уровне AST, или на уровне байт-кода. Обоим этим вариантам свойственны собственные достоинства и недостатки.
Наконец, нужно учитывать, что, хотя, AST в любой реализации Python является общей, процесс трансляции AST в байт-код может отличаться, и в некоторых реализациях Python на промежуточном этапе может генерироваться, скажем, JavaScript, а не байт-код.
Парадигмы из других языков программирования
Преобразование узла внутри AST
Имея AST программы, как преобразовывать отдельные части этого дерева? При помощи удобных встроенных возможностей Python.
Необходимость скопировать поля, описывающие позицию узла в исходном коде, возникает довольно часто. Поэтому в модуле ast есть выделенная функция copy_location как раз для этой цели, и мы можем написать:
Кстати, компилятор CPython уже оптимизирует узлы BinOp так, как показано здесь. Соответствующий код написан на C и приводится в Python/ast_opt.c. Обратите внимание: оптимизатор CPython более универсален и работает не только с числами, как в рассматриваемом нами примере, но с различными типами константных значений.
Проверка узлов в AST
Как убедиться, что сделанные нами преобразования были правильными? Для начала нужно полностью обойти AST и осмотреть всю программу.
Возможно, именно такого поведения вы и добивались, делая pi истинной константной, которая заменяется при компиляции и никогда не может быть переприсвоена, то есть, не может получить другое значение. Однако это, определенно, нарушает семантику Python.
Теперь мы используем узел посетитель, похожий на узел-преобразователь, описанный выше. В отличие от преобразователя, посетитель не предназначен для изменения каких-либо узлов, он просто проходит по AST и осматривает узлы (посещает их). Соответственно, визитирующие методы ничего не возвращают.
В нашем случае мы проверяем, ссылается ли узел Name на pi и делает ли что-либо кроме загрузки значения pi (помним о поле контекста ctx ).
Локальные значения в Python
Наш метод, позволяющий определить, не изменил ли программист значение pi, получился довольно грубым. Тем не менее, компилятор Python действует весьма схожим образом, когда определяет, какие имена в области видимости функции соответствуют локальным переменным. Если переменная изменяется где-либо в области видимости функции (и явно не сделана глобальной, например, при помощи инструкции global), то эта переменная считается локальной во всей области видимости функции.
Следующий пример отлично выполнится и без четвертой строки. Но, хотя x = 0 в четвертой строке никогда и не выполняется, это все равно считается присваиванием к x и, следовательно, x становится локальной переменной в масштабах всей функции, и даже в строке 3. Вот почему Python будет ругаться, что переменная x в третьей строке еще не имеет значения.
Если вас интересуют подробности, как именно здесь работает Python, посмотрите Python/symtable.c.
Заключение
В Python, как и в большинстве языков программирования, конкретная программа не исполняется непосредственно из исходного кода. На самом деле, трансляция исходного кода происходит в два этапа: из него сначала делается абстрактное синтаксическое дерево (AST), а затем байт-код для стековой виртуальной машины. В Python также предоставляется ряд очень приятных возможностей для анализа и даже преобразования AST любой конкретной программы Python, после чего измененное AST можно компилировать и выполнять. Таким образом, мы можем с легкостью внедрить наши собственные оптимизации.
Разумеется, немало деталей я здесь просто опустил. Чтобы убедиться, что ваша оптимизация корректно сработает во всех возможных случаях и обстоятельствах – дело весьма нетривиальное. Однако цель этой статьи – не рассказать вам об оптимизации, готовой для использования в продакшене, а дать базовое представление о том, как Python анализирует ваш программный код, чтобы вы научились его правильно преобразовывать, а затем и оптимизировать.
Асинхронное программирование в Python: краткий обзор
Когда говорят о выполнении программ, то под «асинхронным выполнением» понимают такую ситуацию, когда программа не ждёт завершения некоего процесса, а продолжает работу независимо от него. В качестве примера асинхронного программирования можно привести утилиту, которая, работая асинхронно, делает записи в лог-файл. Хотя такая утилита и может дать сбой (например, из-за нехватки свободного места на диске), в большинстве случаев она будет работать правильно и ей можно будет пользоваться в различных программах. Они смогут её вызывать, передавая ей данные для записи, а после этого смогут продолжать заниматься своими делами.
Применение асинхронных механизмов при написании некоей программы означает, что эта программа будет выполняться быстрее, чем без использования подобных механизмов. При этом то, что планируется запускать асинхронно, вроде утилиты для логирования, должно быть написано с учётом возникновения нештатных ситуаций. Например, утилита для логирования, если место на диске закончилось, может просто прекратить логирование, а не «обваливать» ошибкой основную программу.
Выполнение асинхронного кода обычно подразумевает работу такого кода в отдельном потоке. Это — если речь идёт о системе с одноядерным процессором. В системах с многоядерными процессорами подобный код вполне может выполняться процессом, пользующимся отдельным ядром. Одноядерный процессор в некий момент времени может считывать и выполнять лишь одну инструкцию. Это напоминает чтение книг. Нельзя читать две книги одновременно.
Если вы читаете книгу, а кто-то даёт вам ещё одну книгу, вы можете взять эту вторую книгу и приступить к её чтению. Но первую придётся отложить. По такому же принципу устроено и многопоточное выполнение кода. А если бы несколько ваших копий читало бы сразу несколько книг, то это было бы похоже на то, как работают многопроцессорные системы.
Если на одноядерном процессоре очень быстро переключаться между задачами, требующими разной вычислительной мощности (например — между некими вычислениями и чтением данных с диска), тогда может возникнуть такое ощущение, что единственное процессорное ядро одновременно делает несколько дел. Или, скажем, подобное происходит в том случае, если попытаться открыть в браузере сразу несколько сайтов. Если для загрузки каждой из страниц браузер использует отдельный поток — тогда всё будет сделано гораздо быстрее, чем если бы эти страницы загружались бы по одной. Загрузка страницы — не такая уж и сложная задача, она не использует ресурсы системы по максимуму, в результате одновременный запуск нескольких таких задач оказывается весьма эффективным ходом.
Асинхронное программирование в Python
Изначально в Python для решения задач асинхронного программирования использовались корутины, основанные на генераторах. Потом, в Python 3.4, появился модуль asyncio (иногда его название записывают как async IO ), в котором реализованы механизмы асинхронного программирования. В Python 3.5 появилась конструкция async/await.
Для того чтобы заниматься асинхронной разработкой на Python, нужно разобраться с парой понятий. Это — корутины (coroutine) и задачи (task).
Корутины
Обычно корутина — это асинхронная (async) функция. Корутина может быть и объектом, возвращённым из корутины-функции.
Если при объявлении функции указано то, что она является асинхронной, то вызывать её можно с использованием ключевого слова await :
Такая конструкция означает, что программа будет выполняться до тех пор, пока не встретит await-выражение, после чего вызовет функцию и приостановит своё выполнение до тех пор, пока работа вызванной функции не завершится. После этого возможность запуститься появится и у других корутин.
Задачи
Задачи позволяют запускать корутины в цикле событий. Это упрощает управление выполнением нескольких корутин. Вот пример, в котором используются корутины и задачи. Обратите внимание на то, что сущности, объявленные с помощью конструкции async def — это корутины. Этот пример взят из официальной документации Python.
Если запустить код примера, то на экран будет выведен текст, подобный следующему:
Обратите внимание на то, что отметки времени в первой и последней строках отличаются на 2 секунды. Если же запустить этот пример с последовательным вызовом корутин, то разница между отметками времени составит уже 3 секунды.
Пример
Итоги
Существуют ситуации, в которых использование задач и корутин оказывается весьма полезным Например, если в программе присутствует смесь операций ввода-вывода и вычислений, или если в одной и той же программе выполняются разные вычисления, можно решать эти задачи, запуская код в конкурентном, а не в последовательном режиме. Это способствует сокращению времени, необходимого программе на выполнение определённых действий. Однако это не позволяет, например, выполнять вычисления одновременно. Для организации подобных вычислений применяется мультипроцессинг. Это — отдельная большая тема.
Уважаемые читатели! Как вы пишете асинхронный Python-код?
Сокрытые драгоценности Python
Особенности Python, о которых я даже не подозревал
В последнее время у меня появилось новое хобби – чтение документации Python просто для удовольствия! Когда вы читаете на досуге, то, как правило, замечаете интересные «лакомые кусочки», которые в противном случае пропустили бы. Итак, вот перечень «кусочков», которые заставили меня сказать:
О! Вы можете сделать это на Python?
1. Атрибуты функций
Подобно тому, как вы можете устанавливать атрибуты классов и объектов, вы также можете устанавливать атрибуты функций.
Мы установили атрибуты «optional_return» в строке 10 и «is_awesome» в строке 11. Мы получили доступ к этим атрибутам вне функции позднее в строках 19 и 20. Результат кода:
Это удобно, когда вы хотите, чтобы функция извлекала некую промежуточную переменную, но не возвращала её явно с оператором return каждый раз при вызове функции. Также обратите внимание, что атрибуты могут быть установлены как внутри определения функции, так и вне определения функции.
2. Цикл for-else
В Python вы можете добавить условие else в цикл for. Условие else будет срабатывать только в том случае, если во время выполнения в теле цикла не встретится оператор break.
All elements at least 3 letters long
Обратите внимание, что else имеет отступ на уровне for, а не на уровне if. Здесь ни один элемент не имеет длины короче трёх. Таким образом, никогда не будет встречен оператор break. Следовательно, условие else будет запущено (после выполнения цикла for) и выведется результат, указанный выше.
Можно утверждать, что этого можно достичь с помощью отдельной переменной, которая отслеживает встретился ли оператор break. И, возможно, для другого человека, читающего код, это будет также менее запутанно. Далее приведен эквивалентный путь достижения того же результата:
Думаю, это полезно знать.
3. Разделители для int
Трудно визуально различить целые числа подобные 10000000 и 100000000 (они даже разные числа?). Мы не можем использовать запятые здесь, в Python, подобному тому, как мы используем их в английском языке, потому что Python интерпретирует это как кортеж из нескольких целых чисел.
У Python есть очень удобный способ справиться с этим: мы можем использовать подчеркивание как разделитель для улучшения читабельности. Таким образом, 1_000_000 будет интерпретироваться как целое число.
4. eval () и exec ()
В Python есть возможность динамически считывать строку и обрабатывать её как часть Python кода. Это достигается использованием функций eval() и exec() (‘eval’ для вычисления выражений; и ‘exec’ для выполнения операторов).
В третьей строке функция eval() считывает входную строку как выражение Python, оценивает её и присваивает результат переменной b. В строке 6 функция exec() считывает входную строку как оператор Python и исполняет её.
В целом, в более широком контексте программирования (не только в отношении Python) использование eval/exec невероятно сильно, поскольку позволяет вам писать динамический код, который использует информацию, доступную во время выполнения, для решения проблем, которые не могут быть даже выражены во время компиляции. […] exec – это буквально интерпретатор Python, встроенный в Python, поэтому, если у вас есть особенно сложная проблема для разрешения, один из способов её решить – написать программу для *написания программы для её решения*, затем использовать exec для запуска этой второй программы.
Вы можете прочитать об этом в замечательном объяснении Стивена Д’Апрано.
5. Многоточие (Ellipsis)
5.1. Замена для ненаписанного кода
Подобно pass, константы можно использовать в качестве замены, когда код не полностью написан, но требует некоторого заполнения для синтаксической правильности.
5.2. Альтернатива NONE
None выбирают, когда хотят обозначить пустой ввод или возврат. Но иногда None может быть одним из ожидаемых входных или возвращаемых параметров функций. В этом случае Многоточие может служить заменой.
Функция nth_odd() вычисляет n-ое нечетное число, c учетом n. Функция original_num() вычисляет исходное число n, учитывая n-ое нечетное число. Здесь None – один из ожидаемых входных параметров функции original_num(), так что мы не можем использовать его как замену по умолчанию для аргумента m. Результат кода:
This function needs some input
Non integer input provided to nth_odd() function
9 is 5th odd number
16 is not an odd number
5.3. Нарезка массива в NumPy
NumPy использует многоточие для нарезки массива. Следующий код показывают два эквивалентных способа нарезки:
[ 0 2 4 6 8 10 12 14]
[ 0 2 4 6 8 10 12 14]
Таким образом, ‘…’ показывает, что существует столько ‘:’, сколько необходимо.
Логическое значение Многоточия
TL; DR
Итак, я обнаружил следующие интересные особенности.
Атрибуты Функций: присвоение атрибутов функциям, как и объектам.
Цикл for-else: отслеживание, был ли цикл выполнен без оператора break.
Разделители для int: 32_534_478 – это int.
eval() и exec(): читайте строки как код Python и запустите его.
Многоточие: универсальная встроенная константа.
Напутствие
Python – это не только полезный язык, но и действительно интересный. Все мы заняты своей жизнью, но это не мешает узнавать язык ради него самого. Я бы хотел узнать больше о Пасхальных Яйцах, которые вы, возможно, найдёте.