İşlev bağımsız değişkeni olarak üretici


81

Bir işleve yönelik tek konumsal argüman olarak bir oluşturucu iletmenin neden özel kuralları olduğu açıklanabilir mi?

Eğer sahipsek:

def f(*args):
    print "Success!"
    print args
  1. Bu beklendiği gibi çalışıyor.

    >>> f(1, *[2])
    Success!
    (1, 2)
    
  2. Bu beklendiği gibi çalışmıyor.

    >>> f(*[2], 1)
      File "<stdin>", line 1
    SyntaxError: only named arguments may follow *expression
    
  3. Bu beklendiği gibi çalışıyor

    >>> f(1 for x in [1], *[2])
    Success! 
    (generator object <genexpr> at 0x7effe06bdcd0>, 2)
    
  4. Bu işe yarıyor, ama nedenini anlamıyorum. 2 ile aynı şekilde başarısız olmamalı mı?

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

1
Tam bir kopya değil, ancak oldukça benzer: stackoverflow.com/questions/12720450/… . TL; DR, bir uygulama detayı gibi görünüyor - aynen böyle çalışıyor.
J0HN

2
Not: 2. durum python 3.5+ ile çalışmalıdır ( PEP 448 nedeniyle )
Bakuriu

1
Python 3.5 çıktı ve şimdi durum 3'ün (aslında durum 4) düzeltildiğini söylüyor. Python
3.5'teki

Yanıtlar:


76

Tüm Python sürümlerinde hem 3. hem de 4. sözdizimi hataları olmalıdır . Ancak, Python 2.5 - 3.4 sürümlerini etkileyen ve daha sonra Python sorun izleyicisine gönderilen bir hata buldunuz . Hata nedeniyle, parantezsiz bir üreteç ifadesi, yalnızca *argsve / veya eşlik ediyorsa, bir işlevin argümanı olarak kabul edildi **kwargs. Python 2.6+ hem 3. hem de 4. durumlara izin verirken, Python 2.5 yalnızca 3. duruma izin verir - ancak ikisi de belgelenen dilbilgisine aykırıdır :

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

yani belgeler der bir işlev çağrısı içerir primary, yanında parantez içinde: (a çağrılabilir olarak değerlendirildiğini ifade), ya da bir bağımsız değişken listesi ya da sadece bir parantezsiz jeneratör ifade; ve bağımsız değişken listesi içinde, tüm üreteç ifadeleri parantez içinde olmalıdır.


Bu hata (bilinmemesine rağmen) Python 3.5 ön sürümlerinde düzeltildi. Python 3.5'te, işlevin tek bağımsız değişkeni bu olmadığı sürece, bir üretici ifadesinin etrafında parantezler her zaman gereklidir:

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

Bu şimdi belgelenmiştir Python 3.5 'daki Yenilikler Yeni , bu hatayı tespit DeTeReR sayesinde.


Hatanın analizi

Python 2.6'da , şunlardan sonra anahtar kelime argümanlarının kullanılmasına izin*args veren bir değişiklik yapıldı :

Bir işlev çağrısına * args argümanından sonra anahtar kelime argümanları sağlamak da yasal hale geldi.

>>> def f(*args, **kw):
...     print args, kw
...
>>> f(1,2,3, *(4,5,6), keyword=13)
(1, 2, 3, 4, 5, 6) {'keyword': 13}

Önceden bu bir sözdizimi hatası olurdu. (Katkıda bulunan Amaury Forgeot d'Arc; sayı 3473.)


Bununla birlikte, Python 2.6 dilbilgisi , anahtar kelime argümanları, konumsal argümanlar veya çıplak üreteç ifadeleri arasında herhangi bir ayrım yapmaz - hepsi argumentayrıştırıcı için türdendir.

Python kurallarına göre, işlevin yegane argümanı değilse bir üretici ifadesi parantez içine alınmalıdır. Bu şu şekilde doğrulanır 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;
}

Bununla birlikte, bu işlev hiç dikkate almaz *args- özellikle sadece sıradan konumsal argümanları ve anahtar kelime argümanlarını arar.

Aynı işlevde, anahtar kelime arg'den sonra anahtar kelime olmayan arg için oluşturulan bir hata mesajı vardır :

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;
        }
        ...

Ama bu yine argümanlar için de geçerlidir değil olarak parantezsiz jeneratör ifadeleri kanıtladığı else ifaçıklamada :

else if (TYPE(CHILD(ch, 1)) == gen_for) {
    e = ast_for_genexp(c, ch);
    if (!e)
        return NULL;
    asdl_seq_SET(args, nargs++, e);
}

Böylece, parantezsiz bir üretici ifadesinin kayarak geçmesine izin verildi.


Artık Python 3.5'te *argsbir işlev çağrısının herhangi bir yerinde kullanılabilir , bu nedenle Dilbilgisi buna uyacak şekilde değiştirildi:

arglist: argument (',' argument)*  [',']

ve

argument: ( test [comp_for] |
            test '=' test |
            '**' test |
            '*' test )

ve fordöngü değiştirildi için

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++;
    }
}

Böylece hatayı düzeltir.

Ancak kasıtsız değişiklik, geçerli görünen yapıların

func(i for i in [42], *args)

ve

func(i for i in [42], **kwargs)

parantezsiz bir jeneratörün önüne geçtiği *argsveya **kwargsşimdi çalışmayı durdurduğu.


Bu hatayı bulmak için çeşitli Python sürümlerini denedim. 2.5'te şunları elde edersiniz 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])

Ve bu, Python 3.5'in bazı ön sürümlerinden önce düzeltildi:

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

Bununla birlikte, parantezli oluşturucu ifadesi Python 3.5'te çalışır, ancak Python 3.4'te çalışmaz:

f(*[1], (2 for x in [2]))

Ve bu ipucu. Python 3.5'te *splattinggenelleştirilmiştir; bir işlev çağrısında herhangi bir yerde kullanabilirsiniz:

>>> print(*range(5), 42)
0 1 2 3 4 42

Gerçek böcek (jeneratör ile çalışma Yani *starparantez olmadan) oldu aslında Python 3.5 giderilmiştir ve hata Python 3.4 ve 3.5 arasında değiştiğini şey bu bulunamadı


1
3.5'te düzeltilmedi - sadece oluşturucunun etrafına parens koyun ve davranış aynıdır.
viraptor

1
@viraptor iyi nokta, 3.4'te parantez içindeki ifade bir hata veriyor
Antti Haapala

ha? 3.4.3'te yayınlanıyor: 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'te sözdizimi hatasıdır. Python 3.5'te geçerlidir.
Antti Haapala

Mümkün olsaydı bu cevabı parlatırdım, ilgili C kaynağını dahil ettiğiniz için teşekkürler!
Nick Sweeting
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.