[解決済み] 関数の引数としてのジェネレータ
質問
なぜジェネレータを関数への唯一の位置引数として渡すと、特別なルールがあるように見えるのか、誰か説明できますか?
もし、私たちが
def f(*args):
print "Success!"
print args
-
これは予想通り、動作します。
>>> f(1, *[2]) Success! (1, 2)
-
これは予想通りうまくいきません。
>>> f(*[2], 1) File "<stdin>", line 1 SyntaxError: only named arguments may follow *expression
-
これは予想通り、動作します
>>> f(1 for x in [1], *[2]) Success! (generator object <genexpr> at 0x7effe06bdcd0>, 2)
-
これは動作しますが、なぜか理解できません。2)と同じように失敗してはいけないのでしょうか?
>>> f(*[2], 1 for x in [1]) Success! (generator object <genexpr> at 0x7effe06bdcd0>, 2)
どのように解決するのですか?
3.と4.の両方が表示されない。
は
は全てのPythonのバージョンでシンタックスエラーになります。
しかし、あなたは Python バージョン 2.5 - 3.4 に影響するバグを見つけ、そのバグがその後
Python issue tracker に投稿されました。
. このバグのために、親レスポンス化されていないジェネレータ式は、関数への引数に
*args
と
**kwargs
. Python 2.6+ ではケース 3. と 4. の両方が許可されていましたが、 Python 2.5 ではケース 3. のみでした。
の文書化された文法に反しています。
:
call ::= primary "(" [argument_list [","]
| expression genexpr_for] ")"
つまり、ドキュメントによれば、関数呼び出しは以下のものから構成されます。
primary
(callableとして評価される式)と、その後に括弧で囲まれた
のどちらかです。
引数リスト
または
は、括弧で囲まれていないジェネレータ式です。
で、引数リスト内では、すべてのジェネレータ式は括弧でくくられなければなりません。
このバグは(知られていなかったようですが)、Python 3.5 prereleases で修正されました。Python 3.5 では、関数への唯一の引数でない限り、ジェネレータ式の周りに常に括弧が必要です。
Python 3.5.0a4+ (default:a3f2b171b765, May 19 2015, 16:14:41)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f(1 for i in [42], *a)
File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument
これは現在、文書化された Python 3.5 の新機能 に記載されるようになりました。このバグを発見した DeTeReR に感謝します。
バグの解析
Python 2.6では、以下のような変更がありました。
キーワード引数の使用を許可する
の後に
*args
:
また、関数呼び出しの *args の後にキーワード引数を与えることも合法になりました。
>>> def f(*args, **kw): ... print args, kw ... >>> f(1,2,3, *(4,5,6), keyword=13) (1, 2, 3, 4, 5, 6) {'keyword': 13}
以前は、これは構文エラーだったでしょう。(Contributed by Amaury Forgeot d'Arc、3473号)。
ただし、Python 2.6 の
文法
はキーワード引数、位置引数、あるいは素のジェネレータ式の区別をしません - これらはすべて
argument
というタイプです。
Pythonの規則に従って、ジェネレータ式は、それが関数への唯一の引数でない場合、括弧でくくられなければなりません。これは
Python/ast.c
:
for (i = 0; i < NCH(n); i++) {
node *ch = CHILD(n, i);
if (TYPE(ch) == argument) {
if (NCH(ch) == 1)
nargs++;
else if (TYPE(CHILD(ch, 1)) == gen_for)
ngens++;
else
nkeywords++;
}
}
if (ngens > 1 || (ngens && (nargs || nkeywords))) {
ast_error(n, "Generator expression must be parenthesized "
"if not sole argument");
return NULL;
}
しかし、この関数は
ではなく
を考慮しません。
*args
を全く考慮せず、特に通常の位置引数とキーワード引数のみを探します。
同じ関数のさらに下のほうで、エラーメッセージが キーワード引数の後に非キーワード引数 :
if (TYPE(ch) == argument) {
expr_ty e;
if (NCH(ch) == 1) {
if (nkeywords) {
ast_error(CHILD(ch, 0),
"non-keyword arg after keyword arg");
return NULL;
}
...
しかし、これはまた、引数が
ではない
のように親レス化されていないジェネレータ式は
によって証明されます。
else if
ステートメント
:
else if (TYPE(CHILD(ch, 1)) == gen_for) {
e = ast_for_genexp(c, ch);
if (!e)
return NULL;
asdl_seq_SET(args, nargs++, e);
}
このように、親レスポンス化されていないジェネレータ式が通過することが許可されました。
Python 3.5では、この問題を解決するために
*args
を関数呼び出しのどこでも使うことができるようになりました。
は
文法
はこれに対応するために変更されました。
arglist: argument (',' argument)* [',']
と
argument: ( test [comp_for] |
test '=' test |
'**' test |
'*' test )
とし
for
ループが変更され
から
for (i = 0; i < NCH(n); i++) {
node *ch = CHILD(n, i);
if (TYPE(ch) == argument) {
if (NCH(ch) == 1)
nargs++;
else if (TYPE(CHILD(ch, 1)) == comp_for)
ngens++;
else if (TYPE(CHILD(ch, 0)) == STAR)
nargs++;
else
/* TYPE(CHILD(ch, 0)) == DOUBLESTAR or keyword argument */
nkeywords++;
}
}
こうしてバグを修正する。
しかし、不注意な変更として、有効な外観の構文である
func(i for i in [42], *args)
と
func(i for i in [42], **kwargs)
ここで、親レスポンス化されていないジェネレータは、その前に
*args
または
**kwargs
は動作しなくなりました。
このバグを見つけるために、私はさまざまなPythonのバージョンを試しました。2.5 では、次のようになります。
SyntaxError
:
Python 2.5.5 (r255:77872, Nov 28 2010, 16:43:48)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> f(*[1], 2 for x in [2])
File "<stdin>", line 1
f(*[1], 2 for x in [2])
そして、これは Python 3.5 のいくつかのプレリリースより前に修正されました。
Python 3.5.0a4+ (default:a3f2b171b765, May 19 2015, 16:14:41)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f(*[1], 2 for x in [2])
File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument
しかし、括弧付きのジェネレータ式は、Python 3.5では動作するが、Python 3.4では動作しない。
f(*[1], (2 for x in [2]))
そして、これがヒントです。Python 3.5では
*splatting
は一般化され、関数呼び出しのどこでも使うことができます。
>>> print(*range(5), 42)
0 1 2 3 4 42
というわけで、実際のバグ(ジェネレータが
*star
を括弧無しで使う)
は
は確かに Python 3.5 で修正され、バグは Python 3.4 と 3.5 の間で何が変わったか、ということで見つけることができました。
関連
-
[解決済み] 関数デコレータを作成し、それらを連鎖させるには?
-
[解決済み] 関数内でグローバル変数を使用する
-
[解決済み] 最小限の驚き」と「変更可能なデフォルトの引数
-
[解決済み] モジュールの関数名(文字列)を使って、モジュールの関数を呼び出す。
-
[解決済み] print関数の出力をフラッシュする(pythonの出力をバッファリング解除する)にはどうすればよいですか?
-
[解決済み] Pythonの関数定義における->の意味とは?
-
[解決済み] 関数呼び出しにおけるstarとdoublestarの演算子の意味は?
-
[解決済み] Pythonのargparseを使った隠し引数の作成
-
[解決済み] pandasのタイムゾーンに対応したDateTimeIndexを、特定のタイムゾーンに対応したナイーブなタイムスタンプに変換する。
-
[解決済み] Flask でグローバル変数はスレッドセーフか?リクエスト間でデータを共有するには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 引数のアンパッキング:名前付き引数のみが*式の後に続くことができます。
-
[解決済み] Flaskで1時間ごとに関数を実行するようにスケジュールするには?
-
[解決済み] タプルのリストを複数のリストに変換するには?
-
[解決済み] Python 2.7サポート終了?
-
[解決済み] Pythonのargparseを使った隠し引数の作成
-
[解決済み] 文字列から先頭と末尾のスペースを削除するには?
-
[解決済み] PythonからSMTPを使用してメールを送信する
-
[解決済み] 値で列挙名を取得する [重複]。
-
[解決済み] Pythonでマルチプロセッシングキューを使うには?
-
[解決済み] PythonのRequestsモジュールを使ってWebサイトに "ログイン "するには?