関数の引数としてのジェネレータ


81

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

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

>>> 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)
    

@DanD。f((*[2, 3]), 1)で構文エラーが発生します*-提案についてさらに説明していただけますか?また、問題は「どうやってそれを機能させるか」ではなく、「なぜそれがこのように機能するのか」です。
J0HN 2015

1
正確な複製ではありませんが、非常によく似ています:stackoverflow.com/questions/12720450/…。TL; DRは、実装の詳細のようです-まさにそのように機能します。
J0HN 2015

2
注:ケース2はPython 3.5以降で機能するはずです(PEP 448による
Bakuriu 2015

1
Python 3.5がリリースされ、ケース3(実際にはケース4も)が修正されたことが通知されます。Python 3.5の新機能
Antti Haapala 2015

回答:


76

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

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

つまり、ドキュメントが言うの関数呼び出し備えるprimary、ことにより、括弧内に続く(呼び出し可能に評価をすることを表現)、いずれかの引数リストまたは単にunparenthesizedジェネレータ式。引数リスト内では、すべてのジェネレータ式を括弧で囲む必要があります。


このバグは(知られていないようですが)、Python3.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がこのバグを発見したおかげで、Python3.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による寄稿;発行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])

そして、これはPython3.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では機能しますが、Python3.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で修正され、Python3.4と3.5の間で変更されたバグを見つけることができました。


1
3.5では修正されていません。ジェネレーターの周りにパレンを配置するだけで、動作は同じです。
viraptor 2015

1
@viraptorの良い点、3.4では、括弧で囲まれた式でエラーが発生します
Antti Haapala 2015

え?3.4.3で実行:f(*[1], 1 for x in [1])=>(<generator object <genexpr> at 0x7fa56c889288>, 1)
viraptor 2015

@viraptorf(*[1], (1 for x in [1]))は、Python3.4の構文エラーです。Python3.5で有効です。
アンティハーパラ2015

関連するCソースを含めてくれてありがとう、できればこの答えをギルドします!
ニックスウィーティング2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.