2017-01-27 14 views
8

Czy istnieje sposób utworzenia funkcji, która akceptuje zarówno oddzielne argumenty, jak i krotkę, w postaci Pythonic? tj. osiągnąć coś takiego:Funkcja akceptująca zarówno rozszerzone argumenty, jak i krotkę

def f(*args): 
    """prints 2 values 
      f(1,2) 
       1 2 
      f((1,2)) 
       1 2""" 
    if len(args) == 1: 
     if len(args[0]) != 2: 
      raise Exception("wrong number of arguments") 
     else: 
      print args[0][0],args[0][1] 
    elif len(args) == 2: 
     print args[0],args[1] 
    else: 
      raise Exception("wrong number of arguments") 
+0

Co (oprócz wcięcia) jest nie tak z tym, co napisałeś? –

+4

Twierdzę, że prawdopodobnie nie jest rozsądne, aby twoja funkcja była polimorficzna w ten sposób ... Ale inni mogą się z tym nie zgodzić. – mgilson

+0

Aby echo @mgilson, jeśli twoja funkcja oczekuje dwóch wartości, zadeklaruj, że przyjmuje dwie wartości. Jeśli osoba dzwoniąca ma 2-krotne, może zawsze użyć 'f (* (1,2)). Nie przechylaj się w tył, aby dostosować się do przewidywanego użycia, jeśli zasłania to oczekiwane użycie. – chepner

Odpowiedz

5

Po pierwsze nie wiem, czy to bardzo mądre. Powiedzmy, że dana osoba dzwoni na przykład:

f(*((1,4),(2,5))) 

Jak widać krotka zawiera dwa elementy. Ale teraz z jakiegoś powodu, osoba nazywa to tylko jeden element (bo na przykład generator nie generowane dwa elementy):

f(*((1,4),)) 

Następnie użytkownik prawdopodobnie chcesz, aby funkcja zgłosić to, ale teraz to będzie po prostu ją zaakceptuj (co może prowadzić do skomplikowanego i nieoczekiwanego zachowania). Dobra drukowanie elementów oczywiście nie zaszkodzi. Ale w ogólnym przypadku konsekwencje mogą być bardziej poważne.

Niemniej elegancki sposób to zrobić czyni prostą dekorator że najpierw sprawdza, czy jeden element jest zasilany sprawdza, czy jeden element krotki jest karmione, a jeśli tak rozszerza go:

def one_tuple(f): 
    def g(*args): 
     if len(args) == 1 and isinstance(args[0],tuple): 
      return f(*args[0]) 
     else: 
      return f(*args) 
    return g 

i stosować go Aplikacje f:

@one_tuple 
def f(*args): 
    if len(args) == 2: 
     print args[0],args[1] 
    else: 
     raise Exception("wrong number of arguments") 

dekorator one_tuple ten sposób sprawdza, czy jeden krotka jest podawany, a jeśli tak rozpakowuje to dla ciebie b przed przekazaniem go do swojej funkcji f.

Wskutek f nie musi wziąć pod uwagę przypadek krotny: będzie zawsze być karmione rozszerzonych argumentów i obsługiwać te (oczywiście odwrotnie można zrobić również).

Zaletą definiowania dekoratora jest jego ponowne użycie: można zastosować ten dekorator do wszystkich funkcji (i tym samym ułatwić ich realizację).

+0

Przyjemne ponowne użycie. Podczas gdy cały pomysł jest prawdopodobnie niewystarczający, OP będzie prawdopodobnie musiał to zrobić ponownie, gdy zaczną na tym śliskim zboczu. –

+0

@MadPhysicist: Nie rozumiem, dlaczego musi to zrobić ponownie: jest to prosty dekorator, jeśli zdefiniuje funkcję 'f_alternative', może dołączyć tego samego dekoratora i tym samym pracować dla instancji z krotkami o długości trzech (lub dowolna długość) bez martwienia się o przypadek jedno-tuple. –

+0

Mówiłem, że idea ekspansji jest w najlepszym wypadku wątpliwa, ale OP prawdopodobnie zacznie stosować ją do wielu funkcji. To sprawia, że ​​twoja odpowiedź jest skutecznym sposobem na rozwiązanie złego problemu, stąd +1. "Zrób to" odnosiło się do rozwinięcia argumentów, a nie do pisania osobnego dekoratora dla każdej funkcji. –

3

Nie zgadzam się z tym pomysłem (mimo że podoba mi się to, że pyton nie wymaga definicji typów zmiennych, a tym samym pozwala na coś takiego), ale mogą istnieć przypadki, w których takie rzeczy są potrzebne. Więc tutaj:

def my_f(a, *b): 

    def whatever(my_tuple): 
     # check tuple for conformity 
     # do stuff with the tuple 
     print(my_tuple) 
     return 

    if hasattr(a, '__iter__'): 
     whatever(a) 
    elif b: 
     whatever((a,) + b) 
    else: 
     raise TypeError('malformed input') 
    return 

Restrukturyzacja to trochę, ale logika pozostaje taka sama. jeśli "a" jest wartością iteracyjną, uważaj ją za swoją krotkę, jeśli nie weź pod uwagę "b". jeśli „a” nie jest iterable i „b” nie jest zdefiniowany, podnieść TypeError

my_f((1, 2, 3)) # (1, 2, 3) 
my_f(1, 2, 3) # (1, 2, 3) 
+1

Nie musisz konwertować na 'list (..)' najpierw, wystarczy dołączyć z iteracją. –

+0

Jedynym możliwym problemem jest to, że nie zadziała z pojedynczym nie dającym się uzasadnić argumentem. +1, ponieważ istnieje 90% szans, że OP nie będzie obchodził ten przypadek użycia. –

+0

@MadPhysicist 'join' jest tylko fałszywym przykładem. –

3

pythonowy sposób byłoby użyć kaczka wpisywanie. Będzie działać tylko wtedy, gdy masz pewność, że żaden z rozwiniętych argumentów nie będzie możliwy do sprawdzenia.

def f(*args): 
    def g(*args): 
     # play with guaranteed expanded arguments 

    if len(args) == 1: 
     try: 
      iter(args[0]) 
     except TypeError: 
      pass 
     else: 
      return g(*args[0]) 
    return g(*args) 

Ta implementacja oferuje lekką poprawę na odpowiedź użytkownika @ Ev.Kounis dla przypadków, w których jeden non-krotka argumentem jest przekazywana w. To może również być łatwo przekształcony równoważnej dekoratora opisanej przez @WillemVanOnsem.Użyj wersji dekoratora, jeśli masz więcej niż jedną taką funkcję.

Powiązane problemy