Es un error común en Python establecer un objeto mutable como el valor predeterminado de un argumento en una función. Aquí hay un ejemplo tomado de este excelente artículo de 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']
La explicación de por qué sucede esto está aquí .
Y ahora mi pregunta: ¿Existe un buen caso de uso para esta sintaxis?
Quiero decir, si todos los que lo encuentran cometen el mismo error, lo depuran, comprenden el problema y, a partir de ahí, intentan evitarlo, ¿de qué sirve esa sintaxis?
python
arguments
default-value
mutable
Jonathan
fuente
fuente
__init__
función de una clase, que se establece en una variable de instancia; esto es algo perfectamente válido para querer hacer, y todo sale horriblemente mal con un valor predeterminado mutable. stackoverflow.com/questions/43768055/…Respuestas:
Puede usarlo para almacenar en caché valores entre llamadas a funciones:
def get_from_cache(name, cache={}): if name in cache: return cache[name] cache[name] = result = expensive_calculation() return result
pero generalmente ese tipo de cosas se hace mejor con una clase, ya que luego puede tener atributos adicionales para borrar el caché, etc.
fuente
@functools.lru_cache(maxsize=None)
lru_cache
no está disponible si tiene valores sin hash.La respuesta canónica es esta página: http://effbot.org/zone/default-values.htm
También menciona 3 casos de uso "buenos" para el argumento predeterminado mutable:
fuente
Quizás no mutes el argumento mutable, pero esperas un argumento mutable:
def foo(x, y, config={}): my_config = {'debug': True, 'verbose': False} my_config.update(config) return bar(x, my_config) + baz(y, my_config)
(Sí, sé que puede usarlo
config=()
en este caso particular, pero lo encuentro menos claro y menos general).fuente
import random def ten_random_numbers(rng=random): return [rng.random() for i in xrange(10)]
Utiliza el
random
módulo, efectivamente un singleton mutable, como su generador de números aleatorios predeterminado.fuente
random
una vez por llamada de función" de no-Python . Ambos terminan usando el mismo objeto.EDITAR (aclaración): El problema del argumento predeterminado mutable es un síntoma de una elección de diseño más profunda, es decir, que los valores del argumento predeterminado se almacenan como atributos en el objeto de función. Podría preguntar por qué se hizo esta elección; como siempre, estas preguntas son difíciles de responder correctamente. Pero ciertamente tiene buenos usos:
Optimización para el rendimiento:
def foo(sin=math.sin): ...
Tomando valores de objeto en un cierre en lugar de la variable.
callbacks = [] for i in range(10): def callback(i=i): ... callbacks.append(callback)
fuente
En respuesta a la pregunta de los buenos usos de los valores de argumento predeterminados mutables, ofrezco el siguiente ejemplo:
Un valor predeterminado mutable puede ser útil para programar comandos fáciles de usar e importables de su propia creación. El método predeterminado mutable equivale a tener variables estáticas privadas en una función que puede inicializar en la primera llamada (muy parecido a una clase) pero sin tener que recurrir a globales, sin tener que usar un contenedor y sin tener que instanciar un objeto de clase que se importó. Es elegante a su manera, como espero que estén de acuerdo.
Considere estos dos ejemplos:
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()
Si ejecuta este código, verá que la función dittle () se internaliza en la primera llamada pero no en llamadas adicionales, usa una caché estática privada (la predeterminada mutable) para el almacenamiento estático interno entre llamadas, rechaza los intentos de secuestro el almacenamiento estático, es resistente a la entrada maliciosa y puede actuar en función de las condiciones dinámicas (en este caso, en la cantidad de veces que se ha llamado a la función).
La clave para usar valores predeterminados mutables no es hacer nada que reasigne la variable en la memoria, sino cambiar siempre la variable en su lugar.
Para ver realmente el potencial y la utilidad de esta técnica, guarde este primer programa en su directorio actual con el nombre "DITTLE.py", luego ejecute el siguiente programa. Importa y usa nuestro nuevo comando dittle () sin requerir ningún paso para recordar o programar aros para saltar.
Aquí está nuestro segundo ejemplo. Compile y ejecute esto como un programa nuevo.
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()
¿No es eso lo más resbaladizo y limpio posible? Estos valores predeterminados mutables pueden ser realmente útiles.
========================
Después de reflexionar sobre mi respuesta por un tiempo, no estoy seguro de haber marcado la diferencia entre usar el método predeterminado mutable y la forma habitual de lograr lo mismo con claridad.
La forma habitual es utilizar una función importable que envuelva un objeto Class (y utilice un global). Entonces, para comparar, aquí hay un método basado en clases que intenta hacer las mismas cosas que el método predeterminado mutable.
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()
Guarde este programa basado en clases en su directorio actual como DITTLE.py y luego ejecute el siguiente código (que es el mismo que antes).
from DITTLE import dittle # Nothing to declare, nothing to instantize, nothing to remember. dittle() dittle() dittle() dittle() dittle()
Al comparar los dos métodos, las ventajas de usar un valor predeterminado mutable en una función deberían ser más claras. El método predeterminado mutable no necesita globales, sus variables internas no se pueden configurar directamente. Y aunque el método mutable aceptó un argumento aprobado con conocimiento para un solo ciclo y luego lo ignoró, el método Class se alteró permanentemente porque su variable interna está directamente expuesta al exterior. ¿En cuanto a qué método es más fácil de programar? Creo que eso depende de su nivel de comodidad con los métodos y la complejidad de sus objetivos.
fuente