Warum erlaubt Python Funktionsaufrufe mit falscher Anzahl von Argumenten?

80

Python ist meine erste dynamische Sprache. Ich habe kürzlich einen Funktionsaufruf codiert, der falsch eine falsche Anzahl von Argumenten liefert. Dies schlug mit einer Ausnahme zum Zeitpunkt des Aufrufs dieser Funktion fehl. Ich habe erwartet, dass diese Art von Fehler auch in einer dynamischen Sprache erkannt werden kann, wenn die Quelldatei analysiert wird.

Ich verstehe, dass der Typ der tatsächlichen Argumente erst bekannt ist, wenn die Funktion aufgerufen wird, da dieselbe Variable zu unterschiedlichen Zeiten Werte eines beliebigen Typs enthalten kann. Die Anzahl der Argumente ist jedoch bekannt, sobald die Quelldatei analysiert wurde. Es wird sich nicht ändern, während das Programm ausgeführt wird.

Damit dies keine philosophische Frage ist

Lassen Sie mich die Frage so formulieren, um dies im Rahmen des Stapelüberlaufs zu halten. Gibt es eine Funktion, die Python anbietet und die es erfordert, die Überprüfung der Anzahl der Argumente in einem Funktionsaufruf zu verzögern, bis der Code tatsächlich ausgeführt wird?

Hallo
quelle
17
Was ist, wenn sich der Name zur Laufzeit auf eine andere Funktion bezieht?
Jonrsharpe
Nun, als Gegenargument ist die Anzahl der Argumente zum Aufrufen f(*args)während der Analysephase nicht bekannt.
Łukasz Rogalski
3
Es ist zu beachten, dass in der Typentheorie der Wertetyp die Anzahl der Argumente im Fall von Funktionen enthält.
PyRulez
9
Woher weiß Python, welche Funktion Sie aufrufen werden, bis Sie tatsächlich versuchen, sie aufzurufen? Es ist nicht so, dass Funktionen in Python Namen haben. (Dies ist kein Sarkasmus)
user253751
1
@immibis: Nun, nein. Sie haben Namen. Sie sind jedoch nicht besonders an diese Namen gebunden . Eine Funktion definiert def foo(): whateverhat foo.__name__ == 'foo', aber das wird man nicht mehr aufhören zu tun , foo = some_other_functionoder bar = foo.
user2357112 unterstützt Monica

Antworten:

146

Python kann nicht im Voraus wissen, welches Objekt Sie am Ende aufrufen werden, da Sie das Funktionsobjekt austauschen können, da es dynamisch ist . Jederzeit. Und jedes dieser Objekte kann eine andere Anzahl von Argumenten haben.

Hier ist ein extremes Beispiel:

import random

def foo(): pass
def bar(arg1): pass
def baz(arg1, arg2): pass

the_function = random.choice([foo, bar, baz])
print(the_function())

Der obige Code hat eine 2: 3-Chance, eine Ausnahme auszulösen. Aber Python kann nicht a priori wissen, ob dies der Fall ist oder nicht!

Und ich habe noch nicht einmal mit dynamischen Modulimporten, der Generierung dynamischer Funktionen, anderen aufrufbaren Objekten (jedes Objekt mit einer __call__Methode kann aufgerufen werden) oder Catch-All-Argumenten ( *argsund **kwargs) begonnen.

Um dies besonders deutlich zu machen, geben Sie in Ihrer Frage Folgendes an:

Es wird sich nicht ändern, während das Programm ausgeführt wird.

Dies ist nicht der Fall, nicht in Python. Sobald das Modul geladen ist, können Sie jedes Objekt im Modul-Namespace löschen, hinzufügen oder ersetzen, einschließlich Funktionsobjekte.

Martijn Pieters
quelle
Ich habe schon einmal Funktionstabellen gesehen, daher ist es vielleicht nicht ganz so böse. Oh, warte, die Funktionen in der Tabelle verwenden keine *Argumente ... und das ist Python, also ... ja, ziemlich extrem.
Ray Toal
3
Es ist jetzt ein T-Shirt. (Wenn Sie nicht herausfinden konnten, wie Sie eine Zuordnung hinzufügen können. Wenn Sie es möchten, sagen Sie es mir, und ich kann es wahrscheinlich herausfinden.)
PyRulez
@PyRulez: Mein T-Shirt mit einer einhornförmigen Kopie des Regex-Parsing-Posts verwendet eine URL. Sie könnten einfach hinzufügen https://stackoverflow.com/a/34567789und damit fertig sein.
Martijn Pieters
4
@ PyRulez: aber das ist wirklich nur eine Versandliste. Der Punkt ist zu veranschaulichen, dass Sie Funktionen als Objekte verwenden können und nicht im Voraus wissen können, welche aufgerufen wird. Ansonsten ist es eine einigermaßen übliche Technik .
Martijn Pieters
Vielleicht, um dem Benutzer zu helfen, sein ursprüngliches Problem zu lösen: Wenn der Fehler erkannt wird, bevor der Code ausgeführt wird, kann die Verwendung eines statischen Überprüfungstools erwähnt werden.
Xavier Combelle
35

Die Anzahl der übergebenen Argumente ist bekannt, nicht jedoch die tatsächlich aufgerufene Funktion. Siehe dieses Beispiel:

def foo():
    print("I take no arguments.")

def bar():
    print("I call foo")
    foo()

Dies mag offensichtlich erscheinen, aber lassen Sie uns diese in eine Datei namens "fubar.py" einfügen. Führen Sie nun in einer interaktiven Python-Sitzung Folgendes aus:

>>> import fubar
>>> fubar.foo()
I take no arguments.
>>> fubar.bar()
I call foo
I take no arguments.

Das war offensichtlich. Nun zum lustigen Teil. Wir definieren eine Funktion, die eine Anzahl von Argumenten ungleich Null erfordert:

>>> def notfoo(a):
...    print("I take arguments!")
...

Jetzt machen wir etwas, was man Affenpatching nennt . Wir können tatsächlich die Funktion im Modul ersetzen :foofubar

>>> fubar.foo = notfoo

Wenn wir jetzt anrufen bar, wird ein TypeErrorWille erhoben; Der Name foobezieht sich jetzt auf die oben definierte Funktion anstelle der ursprünglichen Funktion, die früher als bekannt war foo.

>>> fubar.bar()
I call foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/horazont/tmp/fubar.py", line 6, in bar
    foo()
TypeError: notfoo() missing 1 required positional argument: 'a'

Selbst in einer solchen Situation, in der es sehr offensichtlich erscheint, dass die aufgerufene Funktion fookeine Argumente akzeptiert, kann Python nur wissen, dass es tatsächlich die fooFunktion ist, die aufgerufen wird, wenn diese Quellzeile ausgeführt wird.

Dies ist eine Eigenschaft von Python, die es mächtig macht, aber auch einen Teil seiner Langsamkeit verursacht. Tatsächlich wurde vor einiger Zeit auf der Python-Ideen-Mailingliste diskutiert, Module schreibgeschützt zu machen, um die Leistung zu verbessern , aber es wurde keine wirkliche Unterstützung erhalten.

Jonas Schäfer
quelle