83

ジェネレーターを関数への唯一の位置引数として渡すことが特別な規則を持っているように見える理由を誰か説明できますか?

私たちが持っている場合:

def f(*args):
    print "Success!"
    print args
  1. これは期待どおりに機能します。

    >>> f(1, *[2])
    Success!
    (1, 2)
    
  2. 期待どおり、これは機能しません。

    >>> f(*[2], 1)
      File "<stdin>", line 1
    SyntaxError: only named arguments may follow *expression
    
  3. これは期待どおりに機能します

    >>> f(1 for x in [1], *[2])
    Success! 
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
  4. これは機能しますが、理由がわかりません。2)と同じように失敗するべきではありませんか

    >>> f(*[2], 1 for x in [1])
    Success!
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
4

1 に答える 1

78

3. と 4. は両方とも、すべての Python バージョンで構文エラーになるはずです。ただし、Python バージョン 2.5 ~ 3.4 に影響するバグが見つかり、その後Python issue tracker に投稿されました。バグのため、括弧で囲まれていないジェネレーター式は、*argsand/orのみが付随する場合、関数への引数として受け入れられました**kwargs。Python 2.6+ ではケース 3. と 4. の両方が許可されていましたが、Python 2.5 ではケース 3. のみが許可されていましたが、どちらも文書化された文法に反していました。

call    ::=     primary "(" [argument_list [","]
                            | expression genexpr_for] ")"

つまり、ドキュメントによると、関数呼び出しはprimary(callable に評価される式) で構成され、その後に括弧で囲まれた引数リストまたは単に括弧で囲まれていないジェネレーター式が続きます。引数リスト内では、すべてのジェネレーター式を括弧で囲む必要があります。


このバグ (知られていないようですが) は、Python 3.5 プレリリースで修正されていました。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

これは、このバグを発見した DeTeReR のおかげで、 Python 3.5 の新機能 に記載されています。


バグの分析

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}

以前は、これは構文エラーでした。(Amaury Forgeot d'Arc による寄稿; issue 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。具体的には、通常の位置引数とキーワード引数のみを検索します。

同じ関数のさらに下には、キーワード arg の後の非キーワード argに対して生成されたエラー メッセージがあります。

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 の間で何が変更されたかという点で発見できました。

于 2015-09-11T10:47:18.953 に答える