2017-01-27 2 views
8

Gibt es eine Pythonic Möglichkeit, eine Funktion zu erstellen, die sowohl separate Argumente akzeptiert und ein Tupel? d. h., um etwas wie dieses zu erreichen:Funktion, die sowohl erweiterte Argumente und Tupel akzeptiert

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

Was (außer Einrückung) ist falsch mit dem, was Sie geschrieben haben? –

+4

Ich würde argumentieren, dass es wahrscheinlich nicht klug ist, Ihre Funktion auf diese Weise polymorph zu machen ... Aber andere stimmen vielleicht nicht überein. – mgilson

+0

Um @gmilson wiederzugeben, wenn Ihre Funktion zwei Werte erwartet, deklarieren Sie sie so, dass sie zwei Werte annehmen. Wenn ein Aufrufer ein 2-Tupel hat, kann er immer 'f (* (1,2))' 'verwenden. Bücken Sie sich nicht nach hinten, um die erwartete Nutzung zu berücksichtigen, wenn sie die erwartete Nutzung verdeckt. – chepner

Antwort

5

Zunächst einmal weiß ich nicht, ob es sehr klug ist, dies zu tun. Angenommen, eine Person ruft folgende Funktion auf:

Wie Sie sehen können, enthält das Tupel zwei Elemente. Aber jetzt aus irgendeinem Grund ruft die Person, die sie mit nur einem Element (weil beispielsweise der Generator nicht zwei Elemente erzeugt haben):

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

Dann würde der Benutzer wahrscheinlich Ihre Funktion, dies zu melden, aber jetzt wird es akzeptiere es einfach (was zu einem komplizierten und unerwarteten Verhalten führen kann). Okay, das Drucken der Elemente wird natürlich nicht viel schaden. Aber in einem allgemeinen Fall könnten die Konsequenzen schwerwiegender sein.

Trotzdem ein eleganter Weg, dies zu tun, um eine einfache Dekorateur macht dass zuerst überprüft, ob ein Element gespeist wird es überprüft, ob ein Tupelelement feeded ist und wenn ja, dehnt er sich: es

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 

und anwenden auf Ihre f:

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

Der Dekorateur one_tuple somit überprüft, ob ein Tupel zugeführt wird, und wenn ja auspackt es für Sie b bevor Sie es an Ihre f Funktion übergeben.

Als Ergebnis f nicht den Tupel Fall in Rechnung zu tragen hat: es wird immer erweitert Argumente zugeführt werden und diese behandelt (natürlich das Gegenteil so gut gemacht werden könnte).

Der Vorteil der Definition eines Dekorators ist seine Wiederverwendung: Sie können diesen Dekorator auf alle Arten von Funktionen anwenden (und machen es so einfacher, diese zu implementieren).

+0

Nett bei der Wiederverwendung. Während die gesamte Idee wahrscheinlich zunächst defizitär ist, wird OP dies wahrscheinlich erneut tun müssen, sobald sie auf diesem rutschigen Abhang beginnen. –

+0

@MadPhysicist: Ich verstehe nicht, warum er das nochmal machen muss: das ist ein einfacher Dekorator, wenn er eine Funktion 'f_alternative' definiert, kann er den gleichen Dekorator anhängen und so mit Tupeln der Länge drei (oder beliebige Länge), ohne sich um den Ein-Tupel-Fall kümmern zu müssen. –

+0

Ich sagte, dass die Idee der Erweiterung bestenfalls fragwürdig ist, aber OP wird wahrscheinlich anfangen, es auf mehrere Funktionen anzuwenden. Dies macht Ihre Antwort zu einem effizienten Weg, um ein schlechtes Problem zu lösen, daher die +1. "Do this" bezog sich auf die Argument-Erweiterung, nicht auf das Schreiben eines separaten Dekorators für jede Funktion. –

3

Ich stimme der Idee selbst nicht zu (obwohl ich die Tatsache mag, dass Python die Definition von Variablentypen nicht benötigt und somit so etwas erlaubt), aber es könnte Fälle geben, in denen so etwas benötigt wird. Also los gehts:

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 

Neu strukturiert es ein bisschen, aber die Logik bleibt gleich. Wenn "a" ein iterabler Wert ist, betrachten Sie es als Ihr Tupel, wenn Sie nicht auch "b" berücksichtigen. wenn „a“ ist kein durchsuchbar und „b“ nicht definiert ist, heben TypeError

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

Sie müssen nicht zuerst in 'list (..)' konvertieren, einfach das Anhängen mit einem iterablen ist genug. –

+0

Nur möglich Problem ist hier, dass es nicht mit einem einzigen nicht-iterierbaren Argument arbeiten wird. +1, weil es eine Wahrscheinlichkeit von 90% gibt, dass OP sich nicht um diesen Anwendungsfall kümmern wird. –

+0

@MadPhysicist Die 'Join' ist nur ein Dummy-Beispiel. –

3

Die Pythonic Weg wäre Ente eingeben zu verwenden. Dies funktioniert nur, wenn Sie sicher sind, dass keines der erweiterten Argumente iterierbar ist.

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) 

Diese Implementierung bietet eine leichte Verbesserung auf @ Ev.Kounis Antwort für Fälle, in denen ein einzelnen nicht-Tupel Argument übergeben wird. Es kann auch leicht in die entsprechende Dekorateur von @WillemVanOnsem beschrieben gedreht werden.Verwenden Sie die Decorator-Version, wenn Sie mehr als eine Funktion wie diese haben.

Verwandte Themen