Аргумент ctx python что это
Пишем Discord бота на Python используя фреймворк discord.ext
Сегодня мы создадим Discord бота на Python, для этого не надо прикладывать каких то фантастических усилий.
Подготовка к работе
Для начала посещаем портал разработчиков и жмём кнопку «New Application» («Создать приложение»), вводим название нашего будущего бота и жмём «Create» («Создать»).
Диалоговое окно
Теперь нам нужно создать аккаунт для бота — переходим в категорию «Bot» и жмём «Add Bot» («Добавить бота»), в появляющемся диалоговом окне подтвердим это — «Yes, do it!».
Настройки бота
Копируем токен используя соответсвующую кнопку.
Интересный факт: Токен разделён на 3 части с помощью точек. Первая часть — зашифрованый с помощью base64 ID бота, вторая — время создания токена, третья — секретный ключ.
А сейчас нам нужно установить библиотеку discord.py. Для этого нужно использовать утилиту pip.
«Костяк» программной части бота. Эвенты, команды
После установки библиотеки можем приступать к написанию кода.
Почему она должна быть асинхронна? Потому что возьмём для примера команду которую исполняют одновременно 2 раза разные пользователи. def может заставить бота повиснуть изза того что она не может быть исполнена вместе с другими процессами, но async def занимает только один поток вместо того что бы не давать другим процессам программы исполняться.
Этот аргумент хранит информацию про сообщение, автора сообщения, гильдию, канал и т.п. и быть он всегда должен первым аргументом.
И что же мы делаем в ответ на команду? Мы запускаем асинхронную функцию ctx.send что бы отправить сообщение в тот канал в котором была отправлена команда
Тестируем наш костяк
Для начала нам нужно пригласить бота на сервер — и мы возвращаемся на портал разработки, переходим в категорию OAuth2 и в поле «Scopes» отмечаем только галочку на bot и получаем приглашение.
Запускаем бота, и тестируем работает ли команда
Заключение
Возможно в следующих туториалах:
Работа с эмбедами
Разбитие бота на коги
Создание Discord-бота на Python. Часть 1
Версия Python: 3.8.2
Версия discord.py: 1.3.3
Приветствую, хабровчане и другие пользователи интернета. Сегодня я начну цикл статей, посвящённых созданию Discord-бота с помощью библиотеки discord.py. Мы рассмотрим создание как и примитивного бота, как и «продвинутого» бота с модулями. В этой статье мы сделаем стандартную команду и ещё одну небольшую команду. Начнём!
Создание бота и получение токена
Для того, чтобы добавить бота на сервер нужно создать свое приложение и во вкладке General Information скопировать Client ID.
Здесь заменяем CLID на ранее скопированный Client ID.
Во вкладке Bot создаём бота и копируем токен.
Написание кода
Устанавливаем саму библиотеку.
Создаём файл config.py (так удобнее), и создаём там словарь.
Создаём main-файл, название может быть любое.
Импортируем библиотеки и наш файл конфига:
Создаём «тело» бота, название может быть любое:
Начинаем писать основной код.
В конце запускаем бота с помощью:
Должно получится так:
Бонусный туториал!
Сделаем вывод случайных картинок с лисами
Для этого импортируем еще пару библиотек:
Приступим к написанию команды.
Должно получится так:
Конец
На этом 1 часть закончена. Скоро будет опубликована 2 часть.
Commands¶
One of the most appealing aspect of the command extension is how easy it is to define commands and how you can arbitrarily nest groups and commands to have a rich sub-command system.
Commands are defined by attaching it to a regular Python function. The command is then invoked by the user using a similar signature to the Python function.
For example, in the given command definition:
There are two ways of registering a command. The first one is by using Bot.command() decorator, as seen in the example above. The second is using the command() decorator followed by Bot.add_command() on the instance.
Essentially, these two are equivalent:
Since the Bot.command() decorator is shorter and easier to comprehend, it will be the one used throughout the documentation here.
Any parameter that is accepted by the Command constructor can be passed into the decorator. For example, to change the name to something other than the function would be as simple as doing this:
Parameters¶
Since we define commands by making Python functions, we also define the argument passing behaviour by the function parameters.
Certain parameter types do different things in the user side and most forms of parameter types are supported.
Positional¶
The most basic form of parameter passing is the positional parameter. This is where we pass a parameter as-is:
On the bot using side, you can provide positional arguments by just passing a regular string:
To make use of a word with spaces in between, you should quote it:
As a note of warning, if you omit the quotes, you will only get the first word:
Since positional arguments are just regular Python arguments, you can have as many as you want:
Variable¶
Sometimes you want users to pass in an undetermined number of parameters. The library supports this similar to how variable list parameters are done in Python:
This allows our user to accept either one or many arguments as they please. This works similar to positional arguments, so multi-word parameters should be quoted.
For example, on the bot side:
If the user wants to input a multi-word argument, they have to quote it like earlier:
Do note that similar to the Python function behaviour, a user can technically pass no arguments at all:
Keyword-Only Arguments¶
When you want to handle parsing of the argument yourself or do not feel like you want to wrap multi-word user input into quotes, you can ask the library to give you the rest as a single argument. We do this by using a keyword-only argument, seen below:
You can only have one keyword-only argument due to parsing ambiguities.
On the bot side, we do not need to quote input with spaces:
Do keep in mind that wrapping it in quotes leaves it as-is:
By default, the keyword-only arguments are stripped of white space to make it easier to work with. This behaviour can be toggled by the Command.rest_is_raw argument in the decorator.
Invocation Context¶
This parameter gives you access to something called the “invocation context”. Essentially all the information you need to know how the command was executed. It contains a lot of useful information:
Context.guild to fetch the Guild of the command, if any.
Context.message to fetch the Message of the command.
Context.author to fetch the Member or User that called the command.
Context.send() to send a message to the channel the command was used in.
Converters¶
Converters come in a few flavours:
A regular callable object that takes an argument as a sole parameter and returns a different type.
Basic Converters¶
At its core, a basic converter is a callable that takes in an argument and turns it into something else.
For example, if we wanted to add two numbers together, we could request that they are turned into integers for us by specifying the converter:
We specify converters by using something called a function annotation. This is a Python 3 exclusive feature that was introduced in PEP 3107.
This works with any callable, such as a function that would convert a string to all upper-case:
bool¶
Advanced Converters¶
Sometimes a basic converter doesn’t have enough information that we need. For example, sometimes we want to get some information from the Message that called the command or we want to do some asynchronous processing.
An example converter:
The converter provided can either be constructed or not. Essentially these two are equivalent:
If a converter fails to convert an argument to its designated target type, the BadArgument exception must be raised.
Inline Advanced Converters¶
For example, a common idiom would be to have a class and a converter for that class:
This can get tedious, so an inline advanced converter is possible through a classmethod() inside the type:
Discord Converters¶
Working with Discord Models is a fairly common thing when defining commands, as a result the library makes working with them easy.
For example, to receive a Member you can just pass it as a converter:
When this command is executed, it attempts to convert the string given into a Member and then passes it as a parameter for the function. This works by checking if the string is a mention, an ID, a nickname, a username + discriminator, or just a regular username. The default set of converters have been written to be as easy to use as possible.
A lot of discord models work out of the gate as a parameter:
Having any of these set as the converter will intelligently convert the argument to the appropriate target type you specify.
Under the hood, these are implemented by the Advanced Converters interface. A table of the equivalent converter is given below:
By providing the converter it allows us to use them as building blocks for another converter:
Special Converters¶
The command extension also has support for certain converters to allow for more advanced and intricate use cases that go beyond the generic linear parsing. These converters allow you to introduce some more relaxed and dynamic grammar to your commands in an easy to use manner.
typing.Union¶
A typing.Union is a special type hint that allows for the command to take in any of the specific types instead of a singular type. For example, given the following:
typing.Optional¶
A typing.Optional is a special type hint that allows for “back-referencing” behaviour. If the converter fails to parse into the specified type, the parser will skip the parameter and then either None or the specified default will be passed into the parameter instead. The parser will then continue on to the next parameters and converters, if any.
Consider the following example:
This converter only works in regular positional parameters, not variable parameters or keyword-only parameters.
Greedy¶
The Greedy converter is a generalisation of the typing.Optional converter, except applied to a list of arguments. In simple terms, this means that it tries to convert as much as it can until it can’t convert any further.
Consider the following example:
When invoked, it allows for any number of members to be passed in:
The type passed when using this converter depends on the parameter type that it is being attached to:
Positional parameter types will receive either the default parameter or a list of the converted values.
Variable parameter types will be a tuple as usual.
Keyword-only parameter types will be the same as if Greedy was not passed at all.
Greedy parameters can also be made optional by specifying an optional value.
When mixed with the typing.Optional converter you can provide simple and expressive command invocation syntaxes:
This command can be invoked any of the following ways:
The usage of Greedy and typing.Optional are powerful and useful, however as a price, they open you up to some parsing ambiguities that might surprise some people.
For example, a signature expecting a typing.Optional of a discord.Member followed by a int could catch a member named after a number due to the different ways a MemberConverter decides to fetch members. You should take care to not introduce unintended parsing ambiguities in your code. One technique would be to clamp down the expected syntaxes allowed through custom converters or reordering the parameters to minimise clashes.
Error Handling¶
When our commands fail to parse we will, by default, receive a noisy error in stderr of our console that tells us that an error has happened and has been silently ignored.
Most of the time however, we want to handle an error local to the command itself. Luckily, commands come with local error handlers that allow us to do just that. First we decorate an error handler function with Command.error() :
Checks¶
A check is a basic predicate that can take in a Context as its sole parameter. Within it, you have the following options:
Return True to signal that the person can run the command.
Return False to signal that the person cannot run the command.
Raise a CommandError derived exception to signal the person cannot run the command.
To register a check for a command, we would have two ways of doing so. The first is using the check() decorator. For example:
Since an owner check is so common, the library provides it for you ( is_owner() ):
When multiple checks are specified, all of them must be True :
If any of those checks fail in the example above, then the command will not be run.
If you want a more robust error system, you can derive from the exception and raise it instead of returning False :
Global Checks¶
Sometimes we want to apply a check to every command, not just certain commands. The library supports this as well using the global check concept.
Global checks work similarly to regular checks except they are registered with the Bot.check() decorator.
For example, to block all DMs we could do the following:
Be careful on how you write your global checks, as it could also lock you out of your own bot.
Тестируем асинхронный код с помощью 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 анализирует ваш программный код, чтобы вы научились его правильно преобразовывать, а затем и оптимизировать.