함수 인수로 생성기


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

1
정확한 중복은 아니지만 매우 유사합니다 : stackoverflow.com/questions/12720450/… . TL; DR은 구현 세부 사항 인 것처럼 보입니다. 그냥 그렇게 작동합니다.
J0HN 2015 년

2
참고 : 사례 2는 python 3.5 이상에서 작동해야합니다 ( PEP 448 로 인해 )
Bakuriu

1
Python 3.5가 나왔고 이제 케이스 3 (실제로는 케이스 4)이 수정되었음을 알려줍니다. Python 3.5의 새로운 기능
Antti Haapala 2015 년

답변:


76

3.와 4. 모두 모든 Python 버전에서 구문 오류 여야 합니다. 그러나 Python 버전 2.5-3.4에 영향을 미치는 버그를 발견했으며 이후 에 Python 문제 추적기에 게시되었습니다 . 버그로 인해 괄호로 묶지 않은 생성기 표현식이 *args및 / 또는 만 동반 된 경우 함수에 대한 인수로 허용되었습니다 **kwargs. Python 2.6+는 케이스 3과 4를 모두 허용했지만 Python 2.5는 케이스 3 만 허용했지만 둘 다 문서화 된 문법 에 위배되었습니다 .

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

즉, 문서를 말한다의 함수 호출 단계는 primary, 괄호로 이어 (a 호출 평가가 표현) 인수 목록 또는 단지 호로 -이지 발생기 식; 인수 목록 내에서 모든 생성기 표현식은 괄호 안에 있어야합니다.


이 버그는 (알려지지 않은 것처럼 보이지만) 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의 새로운 기능에 문서화되었습니다 .


버그 분석

다음 과 같은 키워드 인수를 사용할 수 있도록 *args Python 2.6이 변경 되었습니다 .

함수 호출에 * 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])

그리고 이것은 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 사이에서 변경된 점에서 발견 될 수 있습니다.


1
3.5에서는 수정되지 않았습니다. 생성기 주변에 괄호를두면 동작이 동일합니다.
viraptor

1
@viraptor 좋은 점은 3.4에서 괄호 표현식은 오류를 제공
안티 Haapala에게

응? 3.4.3에서 실행 : f(*[1], 1 for x in [1])=>(<generator object <genexpr> at 0x7fa56c889288>, 1)
viraptor

@viraptor f(*[1], (1 for x in [1]))는 Python 3.4에서 구문 오류입니다. Python 3.5에서 유효합니다.
Antti Haapala 2015 년

관련 C 소스를 포함 해 주셔서 감사합니다.
Nick Sweeting 2015-09-15
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.