Семантика на разопаковане на кортежи в python

Защо Python позволява само именувани аргументи да следват израз за разопаковане на кортеж в извикване на функция?

>>> def f(a,b,c):
...     print a, b, c
... 
>>> f(*(1,2),3)
  File "<stdin>", line 1
SyntaxError: only named arguments may follow *expression

Дали това е просто естетически избор или има случаи, в които допускането на това би довело до някои неясноти?


person user545424    schedule 23.05.2012    source източник
comment
Можете да добавите последния аргумент към кортежа преди разопаковане.   -  person JBernardo    schedule 23.05.2012
comment
Интересен въпрос. Изглежда си спомням правилата, променени около python 2.5 - преди трябваше да поставите *args и **kwargs в края, но не мога да намеря записа в регистъра на промените.   -  person Nick Craig-Wood    schedule 23.05.2012


Отговори (6)


почти съм сигурен, че причината хората "естествено" да не харесват това е, защото прави значението на по-късните аргументи двусмислено, в зависимост от дължината на интерполираната серия:

def dangerbaby(a, b, *c):
    hug(a)
    kill(b) 

>>> dangerbaby('puppy', 'bug')
killed bug
>>> cuddles = ['puppy']
>>> dangerbaby(*cuddles, 'bug')
killed bug
>>> cuddles.append('kitten')
>>> dangerbaby(*cuddles, 'bug')
killed kitten

не можете да разберете само като погледнете последните две обаждания до dangerbaby кое работи според очакванията и кое убива малки котенца fluffykins.

разбира се, част от тази несигурност присъства и при интерполиране в края. но объркването е ограничено до интерполираната последователност - не засяга други аргументи, като bug.

[Направих бързо търсене, за да видя дали мога да намеря нещо официално. изглежда, че префиксът * за varags е въведен в python 0.9.8. предишният синтаксис е обсъден тук и правилата за това как работи бяха доста сложни. тъй като добавянето на допълнителни аргументи "трябваше" да се случи в края, когато нямаше * маркер, изглежда, че просто се пренася. накрая има споменаване тук на дълга дискусия относно списъците с аргументи, които не беше по имейл.]

person andrew cooke    schedule 23.05.2012

Подозирам, че е за съгласуваност със звездната нотация в дефинициите на функциите, което в крайна сметка е моделът за звездна нотация в извиквания на функции.

В следната дефиниция параметърът *c ще пропусне всички следващи аргументи, които не са ключови думи, така че очевидно, когато се извика f, единственият начин за предаване на стойност за d ще бъде като аргумент за ключова дума.

def f(a, b, *c, d=1):
    print "slurped", len(c)

(Такива „параметри само за ключови думи“ се поддържат само в Python 3. В Python 2 няма начин за присвояване на стойности след аргумент със звезда, така че горното е незаконно.)

Така че в дефиниция на функция аргументът със звезда трябва да следва всички обикновени позиционни аргументи. Това, което забелязахте, е, че същото правило е разширено за извиквания на функции. По този начин звездният синтаксис е последователен за декларации на функции и извиквания на функции.

Друг паралелизъм е, че можете да имате само един аргумент (с една звезда) в извикване на функция. Следното е незаконно, въпреки че човек лесно може да си представи, че е разрешено.

f(*(1,2), *(3,4))
person alexis    schedule 08.06.2012
comment
@Jeff, ти очевидно използваш python 2, който няма параметри само за ключови думи. Актуализирано, за да се изясни това. - person alexis; 09.06.2012
comment
Съжалявам за това! Все още не съм намерил всички разлики между python 2 и 3, предполагам. - person Jeff Tratner; 09.06.2012
comment
изтри моя (безполезен) коментар по-горе. - person Jeff Tratner; 09.06.2012
comment
Е, това ми помогна да изясня този проблем за другите, така че благодаря! - person alexis; 10.06.2012
comment
Една от причините да забраните нещо като f(*(1, 2), *(3, 4)) е, че съответното f(**{'a': 1}, **{'b': 2}) може да доведе до неясноти като f(**{'a': 1}, **{'a': 2}). - person asmeurer; 14.06.2012

На първо място, лесно е сами да предоставите много подобен интерфейс, като използвате функция за обвиване:

def applylast(func, arglist, *literalargs):
  return func(*(literalargs + arglist))

applylast(f, (1, 2), 3)  # equivalent to f(3, 1, 2)

Второ, подобряването на интерпретатора, за да поддържа естествено вашия синтаксис, може да добави излишни разходи към много критичната за производителността дейност на прилагането на функцията. Дори и да изисква само няколко допълнителни инструкции в компилиран код, поради високата употреба на тези рутинни процедури, това може да представлява неприемливо увреждане на производителността в замяна на функция, която не се извиква толкова често и лесно се побира в потребителска библиотека.

person wberry    schedule 23.05.2012

Някои наблюдения:

  1. Python обработва позиционни аргументи преди аргументи на ключови думи (f(c=3, *(1, 2)) във вашия пример все още отпечатва 1 2 3). Това има смисъл, тъй като (i) повечето аргументи в извикванията на функции са позиционни и (ii) семантиката на езика за програмиране трябва да бъде недвусмислена (т.е. трябва да се направи избор по който и да е начин по реда, в който да се обработват позиционни и ключови аргументи ).
  2. Ако имахме позиционен аргумент вдясно при извикване на функция, би било трудно да се определи какво означава това. Ако извикаме f(*(1, 2), 3), трябва ли това да бъде f(1, 2, 3) или f(3, 1, 2) и защо единият избор ще има повече смисъл от другия?
  3. За официално обяснение, PEP 3102 предоставя много информация за това как функционират дефинициите работа. Звездата (*) в дефиниция на функция показва края на аргументите на позицията (раздел Спецификация). За да видите защо, помислете за: def g(a, b, *c, d). Няма начин да предоставите стойност за d освен като аргумент на ключовата дума (позиционните аргументи ще бъдат „грабнати“ от c).
  4. Важно е да разберете какво означава това: тъй като звездата маркира края на позиционните аргументи, това означава, че всички позиционни аргументи трябва да са в тази позиция или вляво от нея.
person Simeon Visser    schedule 12.06.2012

променете реда:

def f(c,a,b):
    print(a,b,c)
f(3,*(1,2))
person Ashwini Chaudhary    schedule 23.05.2012
comment
добре, правило е аргументите да следват този ред: (args,name=args,*args,**args) - person Ashwini Chaudhary; 23.05.2012
comment
Да, но защо е правило? - person Ignacio Vazquez-Abrams; 23.05.2012
comment
Защото, ако можехте да правите нещата в какъвто и да е ред, тогава би било трудно да следвате кода, докато хората пишат нещата по какъвто и да е начин, и тогава може и да не използвате Python. С други думи, защото Гуидо казва така, ето защо :) - person Karmel; 23.05.2012
comment
@Karmel, любопитен съм защо точно аргументите на функцията трябва да са в този ред. Прочетох някои от дискусиите в пощенския списък на PEP и повечето от решенията, които Guido взема, са поради много добри, конкретни и практични причини, а не просто защото той казва така. - person user545424; 24.05.2012
comment
@user545424 Всъщност някои са, защото той казва така като итерация в речника. Итерирането на речник итерира през ключовете, някои хора смятат, че трябва да бъдат ключове, двойки стойности, но той каза, че итерирането само на ключовете е негов инстинкт и обикновено той намира добри причини за инстинктите си по-късно. Това беше от един от разговорите на Google IO, забравих кой. - person jamylak; 12.06.2012

Ако имате параметър само за ключова дума на Python 3, като

def f(*a, b=1):
    ...

тогава може да очаквате нещо като f(*(1, 2), 3) да зададе a на (1 , 2) и b на 3, но разбира се, дори ако синтаксисът, който искате, беше разрешен, това не би било, защото параметрите само за ключови думи трябва да бъдат само за ключови думи, като f(*(1, 2), b=3). Ако беше разрешено, предполагам, че ще трябва да зададе a на (1, 2, 3) и да остави b като 1 по подразбиране. Така че може би не е толкова синтактична неяснота, колкото неяснота в това, което се очаква, което е нещо, което Python много се опитва да избегне.

person asmeurer    schedule 13.06.2012