質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

2115閲覧

discord.py evalにawaitを含められない

Reply

総合スコア21

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2020/08/09 15:37

したいこと

discord.pyでeval機能を作ろうと思っています。

/eval await message.channel.send("test")

みたいな感じに入力すると送信したチャンネルに「test」と帰ってくるようにしたいです。

現在のコード

python

1@client.event 2async def on_message(message): 3 if message.content.startswith("/eval"): 4 args = message.content.splitlines() 5 code = "" 6 for x in args[1:]: 7 code += "\n" + x 8 exec(code)

発生した問題

/eval print("test")

などは問題なく出力されるのですが、awaitが入ると

SyntaxError: 'await' outside function

このようなエラーが出てしまいます。

一応調べて出てきたものは試したのですが、うまくいきませんでした。
どうしたらいいでしょうか?

環境

python 3.8.3
discord.py 1.3.4

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/08/10 03:58

すごいトリッキーなコードがあるようです。 https://stackoverflow.com/questions/44859165/async-exec-in-python async関数内でexec(await含むコード文字列)するのではなく、await含むコード文字列で関数定義をexec内で行い、現在のスコープにないその関数をlocals()から取ってきて自分でawaitして呼び出すという技の模様。確認はしてません。
退会済みユーザー

退会済みユーザー

2020/08/10 04:00

ちなみにexecの中でawaitをasyncio.run()に置き換えて使おうとすると、 "asyncio.run() cannot be called from a running event loop" で怒られました。こういうアプローチしかないんでしょうね。
guest

回答1

0

ベストアンサー

問題点:

  • エラー内容: 予約語 await は async で定義された関数内でしか使えません。※注1

 これは、exec(code) が呼ばれた async def on_message の事ではなく、
exec に渡される code 内のコードの事を指します。

解決策: 単一の「式」で良いなら、await は外側に出して、
await に渡す式のみを評価対象にしましょう。

Pythonにおける式(expression)と文(statement)の違いが判らない場合、
eval と exec の違いを調べて下さい。戻り値が欲しい場合は、eval を用います。

python

1@client.event 2async def on_message(message): 3 if message.content.startswith("/eval await "): 4 src = message.content.split(" ", 2)[-1].lstrip() 5 await eval(src) 6 elif message.content.startswith("/eval"): 7 ...

※ 注1

コルーチンの関数内でなくても使えるようにしようという動きはあって、
Python 3.8 では、コンパイル時のフラグによりこれを可能にできます。

exec/eval ではこのオプションを指定できないので、
compile() 関数を使って コードオブジェクトを生成します。

python

1import ast 2import functools 3 4async_compile = functools.partial(compile, 5 mode="exec", 6 filename="<discord>", 7 flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) 8 9 10@client.event 11async def on_message(message, _eval_prefix="/eval "): 12 if message.content.startswith(_eval_prefix): 13 src = message.content[len(_eval_prefix):].lstrip() 14 code = async_compile(src) 15 await eval(code)

※ 実際に discord.py ではテストしてません。

ひとつ問題があって、await eval(code) ではコルーチンを期待するので、
awaitを含むコード(コルーチン)と、それ以外の普通のコードを区別する必要があります。
print("test") 等は None を返すので、await None ではエラーになります。
これの解決策は下のコードにて。


動作確認に使ったコード (win10/Python3.8で動作確認)

#!/usr/bin/env python3.8 import asyncio import ast from functools import partial async_compile = partial(compile, filename="<stdin>", mode="exec", flags=ast.PyCF_ALLOW_TOP_LEVEL_AWAIT) def async_eval(src, variables=None): if not variables: variables = {} # XXX: コルーチンにするには、最低限一つの await を含む必要が有る為 # コンパイル対象のコードの末尾に await asyncio.sleep(0) を追加してます。 # ここは、他により良い解決策があるかもしれません。 return eval(async_compile("\n".join([src, "await asyncio.sleep(0)"])), globals(), variables) async def main(): from textwrap import dedent A = 10 B = 20 # ローカル変数を渡す例。see also eval/exec の第二第三引数 await async_eval('print("A + B = ", A + B)', locals()) # awaitを含む式のテスト。 await async_eval("await asyncio.sleep(1)") # 複数の await を含むコードのテスト。 # dedentはコードを読みやすくするためのもので、 # 各行頭のインデントを削除した文字列がasync_evalへ渡されます。 await async_eval(dedent(""" print(1) await asyncio.sleep(1) print(2) await asyncio.sleep(1) print(3) """)) await asyncio.sleep(1) print("END") if __name__ == '__main__': asyncio.run(main())

投稿2020/08/10 12:42

編集2020/08/11 05:30
teamikl

総合スコア8760

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Reply

2020/08/11 09:46

エラー内容: 予約語 await は async で定義された関数内でしか使えません。 eval() はasyncで定義されていない関数を呼び出した、ということなのでしょうか? 一応一つ目のコードで実行できましたが、execやevalの使い方も曖昧だったので少し調べてみます。 丁寧な対応ありがとうございます。
teamikl

2020/08/11 10:16

> eval() はasyncで定義されていない関数を呼び出した エラーは await についてなので、これは違います。 eval/exec に渡された文字は await message.channel.send("test") ですが、エラー原因は関数呼び出しではなく async def の関数定義がないのに await を使ってるのが SyntaxError の原因です。 コードを async def ~で囲んで eval/exec に渡す事により、エラー無く通るようになりますが、 期待通りの動作をさせるには、eval/exec 内だけではなく、呼び出し元での await は必須になります。 回答の下で示した例は、コンパイルオプションで async def 以外で await の利用を可能にするアプローチです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問