In Python ist es ein häufiger Fehler, ein veränderbares Objekt als Standardwert für ein Argument in einer Funktion festzulegen. Hier ist ein Beispiel aus diesem hervorragenden Artikel von David Goodger :
>>> def bad_append(new_item, a_list=[]):
a_list.append(new_item)
return a_list
>>> print bad_append('one')
['one']
>>> print bad_append('two')
['one', 'two']
Die Erklärung, warum dies geschieht, ist hier .
Und nun zu meiner Frage: Gibt es einen guten Anwendungsfall für diese Syntax?
Ich meine, wenn jeder, der darauf stößt, den gleichen Fehler macht, ihn debuggt, das Problem versteht und von da an versucht, es zu vermeiden, welchen Nutzen hat eine solche Syntax?
python
arguments
default-value
mutable
Jonathan
quelle
quelle
__init__
Funktion einer Klasse, die in eine Instanzvariable gesetzt wird. Dies ist eine absolut gültige Sache, die man tun möchte, und bei einem veränderlichen Standard geht alles schrecklich schief. stackoverflow.com/questions/43768055/…Antworten:
Sie können es verwenden, um Werte zwischen Funktionsaufrufen zwischenzuspeichern:
def get_from_cache(name, cache={}): if name in cache: return cache[name] cache[name] = result = expensive_calculation() return result
Aber normalerweise wird so etwas mit einer Klasse besser gemacht, da Sie dann zusätzliche Attribute haben können, um den Cache usw. zu leeren.
quelle
@functools.lru_cache(maxsize=None)
lru_cache
ist nicht verfügbar, wenn Sie nicht verwertbare Werte haben.Die kanonische Antwort lautet auf dieser Seite: http://effbot.org/zone/default-values.htm
Außerdem werden 3 "gute" Anwendungsfälle für veränderbare Standardargumente erwähnt:
quelle
Vielleicht mutieren Sie das veränderbare Argument nicht, sondern erwarten ein veränderliches Argument:
def foo(x, y, config={}): my_config = {'debug': True, 'verbose': False} my_config.update(config) return bar(x, my_config) + baz(y, my_config)
(Ja, ich weiß, dass Sie
config=()
in diesem speziellen Fall verwenden können, aber ich finde das weniger klar und weniger allgemein.)quelle
import random def ten_random_numbers(rng=random): return [rng.random() for i in xrange(10)]
Verwendet das
random
Modul, effektiv einen veränderlichen Singleton, als Standard-Zufallszahlengenerator.quelle
random
einmal pro Funktionsaufruf suchen". Beide verwenden dasselbe Objekt.BEARBEITEN (Klarstellung): Das Problem mit veränderlichen Standardargumenten ist ein Symptom für eine tiefere Entwurfsauswahl, nämlich dass Standardargumentwerte als Attribute im Funktionsobjekt gespeichert werden. Sie könnten fragen, warum diese Wahl getroffen wurde; Wie immer sind solche Fragen schwer richtig zu beantworten. Aber es hat sicherlich gute Verwendungsmöglichkeiten:
Leistungsoptimierung:
def foo(sin=math.sin): ...
Abrufen von Objektwerten in einem Abschluss anstelle der Variablen.
callbacks = [] for i in range(10): def callback(i=i): ... callbacks.append(callback)
quelle
Als Antwort auf die Frage nach guten Verwendungsmöglichkeiten für veränderbare Standardargumentwerte biete ich das folgende Beispiel an:
Eine veränderbare Standardeinstellung kann nützlich sein, um benutzerfreundliche, importierbare Befehle Ihrer eigenen Erstellung zu programmieren. Die veränderbare Standardmethode besteht darin, private, statische Variablen in einer Funktion zu haben, die Sie beim ersten Aufruf initialisieren können (ähnlich wie bei einer Klasse), ohne jedoch auf Globals zurückgreifen zu müssen, ohne einen Wrapper verwenden zu müssen und ohne a instanziieren zu müssen Klassenobjekt, das importiert wurde. Es ist auf seine Weise elegant, wie ich hoffe, dass Sie zustimmen werden.
Betrachten Sie diese beiden Beispiele:
def dittle(cache = []): from time import sleep # Not needed except as an example. # dittle's internal cache list has this format: cache[string, counter] # Any argument passed to dittle() that violates this format is invalid. # (The string is pure storage, but the counter is used by dittle.) # -- Error Trap -- if type(cache) != list or cache !=[] and (len(cache) == 2 and type(cache[1]) != int): print(" User called dittle("+repr(cache)+").\n >> Warning: dittle() takes no arguments, so this call is ignored.\n") return # -- Initialize Function. (Executes on first call only.) -- if not cache: print("\n cache =",cache) print(" Initializing private mutable static cache. Runs only on First Call!") cache.append("Hello World!") cache.append(0) print(" cache =",cache,end="\n\n") # -- Normal Operation -- cache[1]+=1 # Static cycle count. outstr = " dittle() called "+str(cache[1])+" times." if cache[1] == 1:outstr=outstr.replace("s.",".") print(outstr) print(" Internal cache held string = '"+cache[0]+"'") print() if cache[1] == 3: print(" Let's rest for a moment.") sleep(2.0) # Since we imported it, we might as well use it. print(" Wheew! Ready to continue.\n") sleep(1.0) elif cache[1] == 4: cache[0] = "It's Good to be Alive!" # Let's change the private message. # =================== MAIN ====================== if __name__ == "__main__": for cnt in range(2):dittle() # Calls can be loop-driven, but they need not be. print(" Attempting to pass an list to dittle()") dittle([" BAD","Data"]) print(" Attempting to pass a non-list to dittle()") dittle("hi") print(" Calling dittle() normally..") dittle() print(" Attempting to set the private mutable value from the outside.") # Even an insider's attempt to feed a valid format will be accepted # for the one call only, and is then is discarded when it goes out # of scope. It fails to interrupt normal operation. dittle([" I am a Grieffer!\n (Notice this change will not stick!)",-7]) print(" Calling dittle() normally once again.") dittle() dittle()
Wenn Sie diesen Code ausführen, werden Sie feststellen, dass die Funktion dittle () beim ersten Aufruf verinnerlicht wird, jedoch nicht bei zusätzlichen Aufrufen. Sie verwendet einen privaten statischen Cache (die veränderbare Standardeinstellung) für die interne statische Speicherung zwischen Aufrufen und lehnt Entführungsversuche ab Der statische Speicher ist widerstandsfähig gegen böswillige Eingaben und kann auf der Grundlage dynamischer Bedingungen (hier abhängig davon, wie oft die Funktion aufgerufen wurde) handeln.
Der Schlüssel zur Verwendung veränderlicher Standardeinstellungen besteht darin, nichts zu tun, was die Variable im Speicher neu zuweist, sondern die Variable immer an Ort und Stelle zu ändern.
Speichern Sie dieses erste Programm in Ihrem aktuellen Verzeichnis unter dem Namen "DITTLE.py" und führen Sie das nächste Programm aus, um die potenzielle Leistungsfähigkeit und Nützlichkeit dieser Technik wirklich zu erkennen. Es importiert und verwendet unseren neuen Befehl dittle (), ohne dass Schritte zum Erinnern oder Programmieren von Reifen zum Durchspringen erforderlich sind.
Hier ist unser zweites Beispiel. Kompilieren Sie dieses und führen Sie es als neues Programm aus.
from DITTLE import dittle print("\n We have emulated a new python command with 'dittle()'.\n") # Nothing to declare, nothing to instantize, nothing to remember. dittle() dittle() dittle() dittle() dittle()
Ist das nicht so glatt und sauber wie möglich? Diese veränderlichen Standardeinstellungen können sehr nützlich sein.
========================
Nachdem ich eine Weile über meine Antwort nachgedacht habe, bin ich mir nicht sicher, ob ich den Unterschied zwischen der Verwendung der veränderlichen Standardmethode und der regulären Methode, dasselbe zu erreichen, klar gemacht habe.
Normalerweise wird eine importierbare Funktion verwendet, die ein Klassenobjekt umschließt (und ein globales Objekt verwendet). Zum Vergleich hier eine klassenbasierte Methode, die versucht, die gleichen Aktionen wie die veränderbare Standardmethode auszuführen.
from time import sleep class dittle_class(): def __init__(self): self.b = 0 self.a = " Hello World!" print("\n Initializing Class Object. Executes on First Call only.") print(" self.a = '"+str(self.a),"', self.b =",self.b,end="\n\n") def report(self): self.b = self.b + 1 if self.b == 1: print(" Dittle() called",self.b,"time.") else: print(" Dittle() called",self.b,"times.") if self.b == 5: self.a = " It's Great to be alive!" print(" Internal String =",self.a,end="\n\n") if self.b ==3: print(" Let's rest for a moment.") sleep(2.0) # Since we imported it, we might as well use it. print(" Wheew! Ready to continue.\n") sleep(1.0) cl= dittle_class() def dittle(): global cl if type(cl.a) != str and type(cl.b) != int: print(" Class exists but does not have valid format.") cl.report() # =================== MAIN ====================== if __name__ == "__main__": print(" We have emulated a python command with our own 'dittle()' command.\n") for cnt in range(2):dittle() # Call can be loop-driver, but they need not be. print(" Attempting to pass arguments to dittle()") try: # The user must catch the fatal error. The mutable default user did not. dittle(["BAD","Data"]) except: print(" This caused a fatal error that can't be caught in the function.\n") print(" Calling dittle() normally..") dittle() print(" Attempting to set the Class variable from the outside.") cl.a = " I'm a griefer. My damage sticks." cl.b = -7 dittle() dittle()
Speichern Sie dieses klassenbasierte Programm in Ihrem aktuellen Verzeichnis als DITTLE.py und führen Sie dann den folgenden Code aus (der mit dem vorherigen identisch ist).
from DITTLE import dittle # Nothing to declare, nothing to instantize, nothing to remember. dittle() dittle() dittle() dittle() dittle()
Durch den Vergleich der beiden Methoden sollten die Vorteile der Verwendung eines veränderlichen Standards in einer Funktion klarer werden. Die veränderbare Standardmethode benötigt keine Globalen, ihre internen Variablen können nicht direkt festgelegt werden. Und während die veränderbare Methode ein sachkundiges übergebenes Argument für einen einzelnen Zyklus akzeptierte und es dann abschüttelte, wurde die Class-Methode dauerhaft geändert, da ihre interne Variable direkt nach außen verfügbar ist. Welche Methode ist einfacher zu programmieren? Ich denke, das hängt von Ihrem Komfortniveau mit den Methoden und der Komplexität Ihrer Ziele ab.
quelle