El "menor asombro" y el argumento predeterminado mutable

Jul 16 2009

Cualquiera que esté jugando con Python el tiempo suficiente ha sido mordido (o hecho pedazos) por el siguiente problema:

def foo(a=[]):
    a.append(5)
    return a

Principiantes de Python que se esperan esta función para volver siempre una lista con un solo elemento: [5]. En cambio, el resultado es muy diferente y muy sorprendente (para un novato):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

Un gerente mío tuvo una vez su primer encuentro con esta característica y la llamó "un defecto de diseño dramático" del lenguaje. Le respondí que el comportamiento tenía una explicación subyacente, y de hecho es muy desconcertante e inesperado si no comprende los aspectos internos. Sin embargo, no pude responder (a mí mismo) la siguiente pregunta: ¿cuál es la razón para vincular el argumento predeterminado en la definición de la función y no en la ejecución de la función? Dudo que el comportamiento experimentado tenga un uso práctico (¿quién realmente usó variables estáticas en C, sin criar errores?)

Editar :

Baczek dio un ejemplo interesante. Junto con la mayoría de sus comentarios y los de Utaal en particular, elaboré más:

>>> def a():
...     print("a executed")
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print(x)
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

Para mí, parece que la decisión de diseño fue relativa a dónde colocar el alcance de los parámetros: ¿dentro de la función o "junto" con ella?

Hacer el enlace dentro de la función significaría que xestá efectivamente enlazado al valor predeterminado especificado cuando la función es llamada, no definida, algo que presentaría un defecto profundo: la deflínea sería "híbrida" en el sentido de que parte del enlace (de el objeto función) ocurriría en la definición, y parte (asignación de parámetros predeterminados) en el momento de la invocación de la función.

El comportamiento real es más consistente: todo lo de esa línea se evalúa cuando se ejecuta esa línea, es decir, en la definición de la función.

Respuestas

1658 rob Jul 18 2009 at 04:29

En realidad, esto no es un defecto de diseño y no se debe a los componentes internos o al rendimiento.
Viene simplemente del hecho de que las funciones en Python son objetos de primera clase, y no solo un fragmento de código.

Tan pronto como lo piense de esta manera, tiene todo el sentido: una función es un objeto que se evalúa según su definición; Los parámetros predeterminados son una especie de "datos de miembros" y, por lo tanto, su estado puede cambiar de una llamada a otra, exactamente como en cualquier otro objeto.

En cualquier caso, Effbot tiene una muy buena explicación de las razones de este comportamiento en Valores de parámetros predeterminados en Python .
Lo encontré muy claro, y realmente sugiero leerlo para conocer mejor cómo funcionan los objetos funcionales.

278 EliCourtwright Jul 16 2009 at 01:11

Suponga que tiene el siguiente código

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

Cuando veo la declaración de eat, lo menos sorprendente es pensar que si no se da el primer parámetro será igual a la tupla ("apples", "bananas", "loganberries")

Sin embargo, se supone que más adelante en el código, hago algo como

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

luego, si los parámetros predeterminados estuvieran vinculados a la ejecución de la función en lugar de la declaración de la función, me sorprendería (de una manera muy mala) descubrir que las frutas se han cambiado. En mi opinión, esto sería más asombroso que descubrir que su foofunción anterior estaba mutando la lista.

El verdadero problema radica en las variables mutables, y todos los lenguajes tienen este problema hasta cierto punto. Aquí hay una pregunta: supongamos que en Java tengo el siguiente código:

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

Ahora, ¿mi mapa usa el valor de la StringBufferclave cuando se colocó en el mapa, o almacena la clave por referencia? De cualquier manera, alguien está asombrado; o la persona que intentó sacar el objeto Mapusando un valor idéntico al que lo puso, o la persona que parece que no puede recuperar su objeto a pesar de que la clave que está usando es literalmente el mismo objeto que se usó para ponerlo en el mapa (esta es realmente la razón por la que Python no permite que sus tipos de datos incorporados mutables se usen como claves de diccionario).

Su ejemplo es bueno para un caso en el que los recién llegados a Python serán sorprendidos y mordidos. Pero yo diría que si "arreglamos" esto, eso solo crearía una situación diferente en la que serían mordidos en su lugar, y esa sería aún menos intuitiva. Además, este es siempre el caso cuando se trata de variables mutables; siempre se encuentra con casos en los que alguien podría esperar intuitivamente uno o el comportamiento opuesto dependiendo del código que esté escribiendo.

Personalmente, me gusta el enfoque actual de Python: los argumentos de la función predeterminada se evalúan cuando la función está definida y ese objeto es siempre el predeterminado. Supongo que podrían usar un caso especial usando una lista vacía, pero ese tipo de carcasa especial causaría aún más asombro, sin mencionar que sería incompatible con versiones anteriores.

244 glglgl Jul 10 2012 at 21:50

La parte relevante de la documentación :

Los valores de los parámetros predeterminados se evalúan de izquierda a derecha cuando se ejecuta la definición de la función. Esto significa que la expresión se evalúa una vez, cuando se define la función, y que se utiliza el mismo valor "precalculado" para cada llamada. Esto es especialmente importante para comprender cuando un parámetro predeterminado es un objeto mutable, como una lista o un diccionario: si la función modifica el objeto (por ejemplo, agregando un elemento a una lista), el valor predeterminado se modifica en efecto. Por lo general, esto no es lo que se pretendía. Una forma de evitar esto es usarlo Nonecomo predeterminado y probarlo explícitamente en el cuerpo de la función, por ejemplo:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin
121 Utaal Jul 16 2009 at 06:21

No sé nada sobre el funcionamiento interno del intérprete de Python (y tampoco soy un experto en compiladores e intérpretes), así que no me culpes si propongo algo insensible o imposible.

Siempre que los objetos de Python sean mutables , creo que esto debería tenerse en cuenta al diseñar los argumentos predeterminados. Cuando crea una instancia de una lista:

a = []

espera obtener una nueva lista referenciada por a.

¿Por qué debería el a=[]in

def x(a=[]):

instanciar una nueva lista en la definición de función y no en la invocación? Es como si estuvieras preguntando "si el usuario no proporciona el argumento, entonces crea una nueva lista y úsala como si la hubiera producido la persona que llama". En cambio, creo que esto es ambiguo:

def x(a=datetime.datetime.now()):

usuario, ¿desea establecer ade forma predeterminada la fecha y hora correspondiente a cuando está definiendo o ejecutando x? En este caso, como en el anterior, mantendré el mismo comportamiento que si el argumento predeterminado "asignación" fuera la primera instrucción de la función ( datetime.now()llamada en la invocación de la función). Por otro lado, si el usuario quisiera el mapeo de tiempo de definición, podría escribir:

b = datetime.datetime.now()
def x(a=b):

Lo sé, lo sé: eso es un cierre. Alternativamente, Python podría proporcionar una palabra clave para forzar el enlace de tiempo de definición:

def x(static a=b):
83 LennartRegebro Jul 16 2009 at 01:54

Bueno, la razón es simplemente que los enlaces se realizan cuando se ejecuta el código y la definición de la función se ejecuta, bueno ... cuando se definen las funciones.

Compare esto:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

Este código sufre exactamente la misma casualidad inesperada. bananas es un atributo de clase y, por lo tanto, cuando le agregas cosas, se agrega a todas las instancias de esa clase. La razón es exactamente la misma.

Es solo "Cómo funciona", y hacer que funcione de manera diferente en el caso de la función probablemente sería complicado, y en el caso de la clase probablemente imposible, o al menos ralentizaría mucho la creación de instancias de objetos, ya que tendría que mantener el código de la clase alrededor y ejecutarlo cuando se creen objetos.

Sí, es inesperado. Pero una vez que cae el centavo, encaja perfectamente con el funcionamiento de Python en general. De hecho, es una buena ayuda para la enseñanza, y una vez que comprenda por qué sucede esto, asimilará Python mucho mejor.

Dicho esto, debería ocupar un lugar destacado en cualquier buen tutorial de Python. Porque, como mencionas, todos se topan con este problema tarde o temprano.

69 DimitrisFasarakisHilliard Dec 09 2015 at 14:13

¿Por qué no haces una introspección?

Estoy realmente sorprendido de que nadie haya realizado la introspección perspicaz que ofrece Python ( 2y 3aplicar) en los llamables.

Dada una pequeña función simple funcdefinida como:

>>> def func(a = []):
...    a.append(5)

Cuando Python lo encuentra, lo primero que hará es compilarlo para crear un codeobjeto para esta función. Mientras se realiza este paso de compilación, Python evalúa * y luego almacena los argumentos predeterminados (una lista vacía []aquí) en el objeto de función en sí . Como mencionó la respuesta principal: la lista aahora puede considerarse un miembro de la función func.

Entonces, hagamos una introspección, un antes y un después para examinar cómo se expande la lista dentro del objeto de función. Estoy usando Python 3.xpara esto, para Python 2 se aplica lo mismo (use __defaults__o func_defaultsen Python 2; sí, dos nombres para lo mismo).

Función antes de la ejecución:

>>> def func(a = []):
...     a.append(5)
...     

Después de que Python ejecute esta definición, tomará los parámetros predeterminados especificados ( a = []aquí) y los incluirá en el __defaults__atributo del objeto de función (sección relevante: Callables):

>>> func.__defaults__
([],)

Ok, entonces una lista vacía como entrada única __defaults__, tal como se esperaba.

Función después de la ejecución:

Ejecutemos ahora esta función:

>>> func()

Ahora, veamos esos de __defaults__nuevo:

>>> func.__defaults__
([5],)

¿Asombrado? ¡El valor dentro del objeto cambia! Las llamadas consecutivas a la función ahora simplemente se agregarán a ese listobjeto incrustado :

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

Entonces, ahí lo tiene, la razón por la que ocurre este 'defecto' es porque los argumentos predeterminados son parte del objeto de la función. No hay nada extraño aquí, todo es un poco sorprendente.

La solución común para combatir esto es usar Nonecomo predeterminado y luego inicializar en el cuerpo de la función:

def func(a = None):
    # or: a = [] if a is None else a
    if a is None:
        a = []

Dado que el cuerpo de la función se ejecuta de nuevo cada vez, siempre obtiene una nueva lista vacía nueva si no se pasó ningún argumento a.


Para verificar aún más que la lista __defaults__es la misma que la utilizada en la función func, puede simplemente cambiar su función para devolver el idde la lista autilizada dentro del cuerpo de la función. Luego, compárelo con la lista en __defaults__(posición [0]en __defaults__) y verá cómo estos se refieren a la misma instancia de lista:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

¡Todo con el poder de la introspección!


* Para verificar que Python evalúa los argumentos predeterminados durante la compilación de la función, intente ejecutar lo siguiente:

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

como notará, input()se llama antes de que barse realice el proceso de creación de la función y vincularla al nombre .

59 Brian Jul 16 2009 at 17:05

Solía ​​pensar que crear los objetos en tiempo de ejecución sería el mejor enfoque. Ahora estoy menos seguro, ya que se pierden algunas funciones útiles, aunque puede valer la pena de todos modos simplemente para evitar la confusión de los novatos. Las desventajas de hacerlo son:

1. Desempeño

def foo(arg=something_expensive_to_compute())):
    ...

Si se usa la evaluación en tiempo de llamada, entonces se llama a la función costosa cada vez que se usa su función sin un argumento. O pagaría un precio caro en cada llamada o necesitaría almacenar en caché manualmente el valor externamente, contaminando su espacio de nombres y agregando verbosidad.

2. Forzar parámetros vinculados

Un truco útil es vincular los parámetros de una lambda a la vinculación actual de una variable cuando se crea la lambda. Por ejemplo:

funcs = [ lambda i=i: i for i in range(10)]

Esto devuelve una lista de funciones que devuelven 0,1,2,3 ... respectivamente. Si se cambia el comportamiento, en su lugar se vincularán ial valor de tiempo de llamada de i, por lo que obtendría una lista de funciones que devolvieron todas 9.

De lo contrario, la única forma de implementar esto sería crear un cierre adicional con el límite i, es decir:

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3. Introspección

Considere el código:

def foo(a='test', b=100, c=[]):
   print a,b,c

Podemos obtener información sobre los argumentos y los valores predeterminados utilizando el inspectmódulo, que

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

Esta información es muy útil para cosas como generación de documentos, metaprogramación, decoradores, etc.

Ahora, suponga que el comportamiento de los valores predeterminados podría cambiarse para que sea el equivalente de:

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

Sin embargo, hemos perdido la capacidad de hacer una introspección y ver cuáles son los argumentos predeterminados . Debido a que los objetos no han sido construidos, nunca podemos conseguirlos sin llamar a la función. Lo mejor que podemos hacer es almacenar el código fuente y devolverlo como una cadena.

55 LutzPrechelt Mar 30 2015 at 18:18

5 puntos en defensa de Python

  1. Sencillez : El comportamiento es simple en el siguiente sentido: la mayoría de las personas caen en esta trampa solo una vez, no varias veces.

  2. Consistencia : Python siempre pasa objetos, no nombres. El parámetro predeterminado es, obviamente, parte del encabezado de la función (no el cuerpo de la función). Por lo tanto, debe evaluarse en el momento de la carga del módulo (y solo en el momento de la carga del módulo, a menos que esté anidado), no en el momento de la llamada a la función.

  3. Utilidad : Como señala Frederik Lundh en su explicación de "Valores de parámetros predeterminados en Python" , el comportamiento actual puede ser bastante útil para la programación avanzada. (Utilizar con moderación.)

  4. Documentación suficiente : En la documentación más básica de Python, el tutorial, el problema se anuncia en voz alta como una "Advertencia importante" en la primera subsección de la Sección "Más sobre la definición de funciones" . La advertencia incluso utiliza negrita, que rara vez se aplica fuera de los encabezados. RTFM: Lea el manual fino.

  5. Metaaprendizaje : caer en la trampa es en realidad un momento muy útil (al menos si eres un aprendiz reflexivo), porque posteriormente comprenderás mejor el punto "Consistencia" anterior y eso te enseñará mucho sobre Python.

53 ymv Jul 16 2009 at 02:15

Este comportamiento se explica fácilmente por:

  1. La declaración de función (clase, etc.) se ejecuta solo una vez, creando todos los objetos de valor predeterminados
  2. todo se pasa por referencia

Asi que:

def x(a=0, b=[], c=[], d=0):
    a = a + 1
    b = b + [1]
    c.append(1)
    print a, b, c
  1. a no cambia - cada llamada de asignación crea un nuevo objeto int - se imprime un nuevo objeto
  2. b no cambia: la nueva matriz se construye a partir del valor predeterminado y se imprime
  3. c cambios - la operación se realiza en el mismo objeto - y se imprime
35 GlennMaynard Jul 16 2009 at 03:18

Lo que estás preguntando es por qué esto:

def func(a=[], b = 2):
    pass

no es internamente equivalente a esto:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

excepto en el caso de llamar explícitamente a func (None, None), que ignoraremos.

En otras palabras, en lugar de evaluar los parámetros predeterminados, ¿por qué no almacenar cada uno de ellos y evaluarlos cuando se llama a la función?

Probablemente una respuesta esté ahí: efectivamente convertiría cada función con parámetros predeterminados en un cierre. Incluso si todo está oculto en el intérprete y no es un cierre completo, los datos deben almacenarse en algún lugar. Sería más lento y usaría más memoria.

35 hynekcer Nov 23 2012 at 01:09

1) El llamado problema de "Argumento predeterminado mutable" es, en general, un ejemplo especial que demuestra que:
"Todas las funciones con este problema sufren también un problema de efectos secundarios similar en el parámetro real "
. generalmente indeseable y deben arreglarse ambos juntos.

Ejemplo:

def foo(a=[]):                 # the same problematic function
    a.append(5)
    return a

>>> somevar = [1, 2]           # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5]                      # usually expected [1, 2]

Solución : una copia
Una solución absolutamente segura es primero copyo deepcopyel objeto de entrada y luego hacer lo que sea con la copia.

def foo(a=[]):
    a = a[:]     # a copy
    a.append(5)
    return a     # or everything safe by one line: "return a + [5]"

Muchos tipos mutables incorporados tienen un método de copia como some_dict.copy()o some_set.copy()o se pueden copiar fácilmente como somelist[:]o list(some_list). Cada objeto también puede ser copiado por copy.copy(any_object)o más completo por copy.deepcopy()(este último útil si el objeto mutable está compuesto por objetos mutables). Algunos objetos se basan fundamentalmente en efectos secundarios como el objeto "archivo" y no se pueden reproducir de forma significativa mediante copia. proceso de copiar

Problema de ejemplo para una pregunta SO similar

class Test(object):            # the original problematic class
  def __init__(self, var1=[]):
    self._var1 = var1

somevar = [1, 2]               # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar                  # [1, 2, [1]] but usually expected [1, 2]
print t2._var1                 # [1, 2, [1]] but usually expected [1, 2]

No debe guardarse en ningún atributo público de una instancia devuelta por esta función. (Suponiendo que los atributos privados de instancia no deben modificarse desde fuera de esta clase o subclases por convención. Es decir, _var1es un atributo privado)

Conclusión:
Los objetos de parámetros de entrada no deben modificarse en su lugar (mutados) ni tampoco deben vincularse a un objeto devuelto por la función. (Si preferimos la programación sin efectos secundarios, lo cual se recomienda encarecidamente, consulte Wiki sobre "efectos secundarios" (los dos primeros párrafos son relevantes en este contexto).)

2)
Solo si se requiere el efecto secundario en el parámetro real pero no se desea en el parámetro predeterminado, la solución útil es def ...(var1=None): if var1 is None: var1 = [] Más ..

3) En algunos casos es útil el comportamiento mutable de los parámetros predeterminados .

31 Ben May 23 2011 at 11:24

En realidad, esto no tiene nada que ver con los valores predeterminados, aparte de que a menudo surge como un comportamiento inesperado cuando escribe funciones con valores predeterminados mutables.

>>> def foo(a):
    a.append(5)
    print a

>>> a  = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]

No hay valores predeterminados a la vista en este código, pero tiene exactamente el mismo problema.

El problema es que fooestá modificando una variable mutable pasada desde la persona que llama, cuando la persona que llama no espera esto. Un código como este estaría bien si la función se llamara algo como append_5; entonces la persona que llama estaría llamando a la función para modificar el valor que pasa, y se esperaría el comportamiento. Pero es muy poco probable que una función de este tipo acepte un argumento predeterminado, y probablemente no devolvería la lista (dado que la persona que llama ya tiene una referencia a esa lista; la que acaba de pasar).

Su original foo, con un argumento predeterminado, no debería modificar asi se pasó explícitamente u obtuvo el valor predeterminado. Su código debe dejar los argumentos mutables solos a menos que esté claro por el contexto / nombre / documentación que se supone que los argumentos deben ser modificados. Usar valores mutables pasados ​​como argumentos como temporales locales es una idea extremadamente mala, ya sea que estemos en Python o no y si hay argumentos predeterminados involucrados o no.

Si necesita manipular destructivamente un temporal local en el curso de calcular algo, y necesita comenzar su manipulación desde un valor de argumento, necesita hacer una copia.

27 Stéphane Mar 27 2015 at 06:14

Ya es un tema muy ocupado, pero por lo que leí aquí, lo siguiente me ayudó a darme cuenta de cómo funciona internamente:

def bar(a=[]):
     print id(a)
     a = a + [1]
     print id(a)
     return a

>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object 
[1]
>>> id(bar.func_defaults[0])
4484370232
25 JasonBaker Jul 16 2009 at 06:18

Es una optimización del rendimiento. Como resultado de esta funcionalidad, ¿cuál de estas dos llamadas a función cree que es más rápida?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

Te daré una pista. Aquí está el desmontaje (verhttp://docs.python.org/library/dis.html):

#1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

#2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

Dudo que el comportamiento experimentado tenga un uso práctico (¿quién realmente usó variables estáticas en C, sin criar errores?)

Como se puede ver, no es una ventaja en el rendimiento cuando se usan parámetros por defecto inmutables. Esto puede marcar la diferencia si se trata de una función llamada con frecuencia o si el argumento predeterminado tarda mucho en construirse. Además, tenga en cuenta que Python no es C. En C tiene constantes que son prácticamente gratuitas. En Python no tienes este beneficio.

25 AaronHall May 01 2016 at 23:20

Python: el argumento predeterminado mutable

Los argumentos predeterminados se evalúan en el momento en que la función se compila en un objeto de función. Cuando la función los usa varias veces, son y siguen siendo el mismo objeto.

Cuando son mutables, cuando se mutan (por ejemplo, al agregarle un elemento) permanecen mutados en llamadas consecutivas.

Permanecen mutados porque son el mismo objeto cada vez.

Código equivalente:

Dado que la lista está vinculada a la función cuando el objeto de la función se compila y se crea una instancia, esto:

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

es casi exactamente equivalente a esto:

_a_list = [] # create a list in the globals

def foo(mutable_default_argument=_a_list): # make it the default argument
    """function that uses a list"""

del _a_list # remove globals name binding

Demostración

Aquí hay una demostración: puede verificar que son el mismo objeto cada vez que se hace referencia a ellos por

  • viendo que la lista se crea antes de que la función haya terminado de compilarse en un objeto de función,
  • observando que el id es el mismo cada vez que se hace referencia a la lista,
  • observando que la lista permanece cambiada cuando se llama por segunda vez a la función que la usa,
  • observando el orden en el que se imprime la salida desde la fuente (que convenientemente numeré para usted):

example.py

print('1. Global scope being evaluated')

def create_list():
    '''noisily create a list for usage as a kwarg'''
    l = []
    print('3. list being created and returned, id: ' + str(id(l)))
    return l

print('2. example_function about to be compiled to an object')

def example_function(default_kwarg1=create_list()):
    print('appending "a" in default default_kwarg1')
    default_kwarg1.append("a")
    print('list with id: ' + str(id(default_kwarg1)) + 
          ' - is now: ' + repr(default_kwarg1))

print('4. example_function compiled: ' + repr(example_function))


if __name__ == '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

y ejecutarlo con python example.py:

1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']

¿Viola esto el principio de "menor asombro"?

Este orden de ejecución suele confundir a los nuevos usuarios de Python. Si comprende el modelo de ejecución de Python, entonces se vuelve bastante esperado.

La instrucción habitual para los nuevos usuarios de Python:

Pero esta es la razón por la que la instrucción habitual para los nuevos usuarios es crear sus argumentos predeterminados como este en su lugar:

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

Esto usa el singleton None como un objeto centinela para decirle a la función si hemos obtenido o no un argumento diferente al predeterminado. Si no obtenemos ningún argumento, entonces realmente queremos usar una nueva lista vacía [], como predeterminada.

Como dice la sección del tutorial sobre el flujo de control :

Si no desea que se comparta el valor predeterminado entre llamadas posteriores, puede escribir la función de esta manera:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
24 Baczek Jul 16 2009 at 19:19

La respuesta más corta probablemente sería "la definición es ejecución", por lo tanto, todo el argumento no tiene sentido estricto. Como un ejemplo más elaborado, puede citar esto:

def a(): return []

def b(x=a()):
    print x

Con suerte, es suficiente para mostrar que no ejecutar las expresiones de argumento predeterminadas en el momento de la ejecución de la defdeclaración no es fácil o no tiene sentido, o ambas cosas.

Sin embargo, estoy de acuerdo en que es un problema cuando intentas usar constructores predeterminados.

21 DmitryMinkovsky Apr 25 2012 at 02:43

Este comportamiento no es sorprendente si tiene en cuenta lo siguiente:

  1. El comportamiento de los atributos de clase de solo lectura en los intentos de asignación, y que
  2. Las funciones son objetos (se explican bien en la respuesta aceptada).

El papel de (2) se ha cubierto ampliamente en este hilo. (1) es probablemente el factor que causa asombro, ya que este comportamiento no es "intuitivo" cuando proviene de otros idiomas.

(1) se describe en el tutorial de Python sobre clases . En un intento de asignar un valor a un atributo de clase de solo lectura:

... todas las variables que se encuentran fuera del alcance más interno son de solo lectura ( un intento de escribir en dicha variable simplemente creará una nueva variable local en el alcance más interno, dejando la variable externa con el mismo nombre sin cambios ).

Vuelva al ejemplo original y considere los puntos anteriores:

def foo(a=[]):
    a.append(5)
    return a

Aquí foohay un objeto y aes un atributo de foo(disponible en foo.func_defs[0]). Dado que aes una lista, aes mutable y, por lo tanto, es un atributo de lectura-escritura de foo. Se inicializa a la lista vacía según lo especificado por la firma cuando se crea una instancia de la función, y está disponible para lectura y escritura siempre que exista el objeto de función.

Llamar foosin anular un valor predeterminado utiliza ese valor predeterminado de foo.func_defs. En este caso, foo.func_defs[0]se usa para adentro del alcance del código del objeto de función. Cambios al acambio foo.func_defs[0], que es parte del fooobjeto y persiste entre la ejecución del código en foo.

Ahora, compare esto con el ejemplo de la documentación sobre la emulación del comportamiento de argumento predeterminado de otros lenguajes , de modo que los valores predeterminados de la firma de la función se utilizan cada vez que se ejecuta la función:

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

Teniendo en cuenta (1) y (2) , se puede ver por qué esto logra el comportamiento deseado:

  • Cuando se crea una fooinstancia del objeto de función, foo.func_defs[0]se establece en Noneun objeto inmutable.
  • Cuando la función se ejecuta con valores predeterminados (sin ningún parámetro especificado Len la llamada a la función), foo.func_defs[0]( None) está disponible en el ámbito local como L.
  • Sobre L = [], la asignación no puede tener éxito en foo.func_defs[0], porque ese atributo es de solo lectura.
  • Según (1) , se crea una nueva variable local también nombrada Len el ámbito local y se utiliza para el resto de la llamada a la función. foo.func_defs[0]por lo tanto, permanece sin cambios para futuras invocaciones de foo.
20 hugo24 Feb 28 2013 at 18:10

Una solución simple usando Ninguno

>>> def bar(b, data=None):
...     data = data or []
...     data.append(b)
...     return data
... 
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]
19 Alexander Sep 12 2015 at 13:00

Voy a demostrar una estructura alternativa para pasar un valor de lista predeterminado a una función (funciona igualmente bien con los diccionarios).

Como otros han comentado extensamente, el parámetro de lista está vinculado a la función cuando se define en lugar de cuando se ejecuta. Debido a que las listas y los diccionarios son mutables, cualquier alteración de este parámetro afectará a otras llamadas a esta función. Como resultado, las llamadas posteriores a la función recibirán esta lista compartida que puede haber sido alterada por cualquier otra llamada a la función. Peor aún, dos parámetros están usando el parámetro compartido de esta función al mismo tiempo ajenos a los cambios realizados por el otro.

Método incorrecto (probablemente ...) :

def foo(list_arg=[5]):
    return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]  

# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()             
7

Puede verificar que son el mismo objeto usando id:

>>> id(a)
5347866528

>>> id(b)
5347866528

Según "Python efectivo: 59 formas específicas de escribir mejor Python" de Brett Slatkin, elemento 20: uso Noney cadenas de documentos para especificar argumentos dinámicos predeterminados (p. 48)

La convención para lograr el resultado deseado en Python es proporcionar un valor predeterminado Noney documentar el comportamiento real en la cadena de documentos.

Esta implementación asegura que cada llamada a la función reciba la lista predeterminada o la lista pasada a la función.

Método preferido :

def foo(list_arg=None):
   """
   :param list_arg:  A list of input values. 
                     If none provided, used a list with a default value of 5.
   """
   if not list_arg:
       list_arg = [5]
   return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
>>> b
[5, 7]

c = foo([10])
c.append(11)
>>> c
[10, 11]

Puede haber casos de uso legítimos para el 'Método incorrecto' en los que el programador pretendía que se compartiera el parámetro de lista predeterminado, pero esto es más probable que sea la excepción que la regla.

17 Marcin Mar 21 2012 at 00:22

Las soluciones aquí son:

  1. Úselo Nonecomo su valor predeterminado (o un nonce object) y actívelo para crear sus valores en tiempo de ejecución; o
  2. Use a lambdacomo su parámetro predeterminado y llámelo dentro de un bloque try para obtener el valor predeterminado (este es el tipo de cosas para las que sirve la abstracción lambda).

La segunda opción es buena porque los usuarios de la función pueden pasar un invocable, que puede que ya exista (como un type)

16 joedborg Jan 15 2013 at 18:02

Puede evitar esto reemplazando el objeto (y, por lo tanto, el lazo con el alcance):

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

Feo, pero funciona.

16 Saish Sep 12 2014 at 05:05

Cuando hacemos esto:

def foo(a=[]):
    ...

... asignamos el argumento aa una lista sin nombre , si la persona que llama no pasa el valor de a.

Para simplificar las cosas de esta discusión, démosle un nombre temporalmente a la lista sin nombre. ¿Qué tal pavlo?

def foo(a=pavlo):
   ...

En cualquier momento, si la persona que llama no nos dice qué aes, lo reutilizamos pavlo.

Si pavloes mutable (modificable) y footermina modificándolo, foose llama a un efecto que notamos la próxima vez sin especificarlo a.

Así que esto es lo que ves (recuerda, pavlose inicializa en []):

 >>> foo()
 [5]

Ahora pavloes [5].

Llamar de foo()nuevo modifica de pavlonuevo:

>>> foo()
[5, 5]

Especificar acuándo llamar foo()garantiza pavloque no se toque.

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

Entonces, pavlotodavía lo es [5, 5].

>>> foo()
[5, 5, 5]
16 bgreen-litl Feb 06 2015 at 04:44

A veces aprovecho este comportamiento como alternativa al siguiente patrón:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

Si singletonsolo lo usa use_singleton, me gusta el siguiente patrón como reemplazo:

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

He usado esto para crear instancias de clases de cliente que acceden a recursos externos, y también para crear dictados o listas para memorización.

Dado que no creo que este patrón sea muy conocido, hago un breve comentario para evitar futuros malentendidos.

13 ChristosHayward Jul 17 2009 at 02:17

Puede ser cierto que:

  1. Alguien está usando todas las funciones de idiomas / bibliotecas, y
  2. Cambiar el comportamiento aquí no sería aconsejable, pero

es totalmente coherente mantener las dos características anteriores y aún hacer otro punto:

  1. Es una característica confusa y desafortunada en Python.

Las otras respuestas, o al menos algunas de ellas, hacen los puntos 1 y 2 pero no el 3, o hacen el punto 3 y restan importancia a los puntos 1 y 2. Pero los tres son ciertos.

Puede ser cierto que cambiar de caballos a mitad de camino aquí requeriría una rotura significativa, y que podría haber más problemas creados al cambiar Python para manejar intuitivamente el fragmento de apertura de Stefano. Y puede ser cierto que alguien que conociera bien los aspectos internos de Python podría explicar un campo minado de consecuencias. Sin embargo,

El comportamiento existente no es Pythonic, y Python tiene éxito porque muy poco del lenguaje viola el principio del menor asombro en cualquier lugar cercano a este. Es un problema real, sea conveniente o no desarraigarlo. Es un defecto de diseño. Si comprende el lenguaje mucho mejor al tratar de rastrear el comportamiento, puedo decir que C ++ hace todo esto y más; aprendes mucho navegando, por ejemplo, errores sutiles de puntero. Pero esto no es Pythonic: las personas que se preocupan por Python lo suficiente como para perseverar frente a este comportamiento son personas que se sienten atraídas por el lenguaje porque Python tiene muchas menos sorpresas que otros lenguajes. Los aficionados y los curiosos se vuelven Pythonistas cuando se asombran del poco tiempo que se tarda en hacer que algo funcione, no por un diseño fl, es decir, un rompecabezas de lógica oculta, que va en contra de las intuiciones de los programadores que se sienten atraídos por Python. porque simplemente funciona .

10 MarkRansom Oct 18 2017 at 00:38

Esto no es un defecto de diseño . Cualquiera que se tropiece con esto está haciendo algo mal.

Hay 3 casos que veo en los que puede encontrarse con este problema:

  1. Tiene la intención de modificar el argumento como efecto secundario de la función. En este caso, nunca tiene sentido tener un argumento predeterminado. La única excepción es cuando abusa de la lista de argumentos para tener atributos de función, por ejemplo cache={}, y no se espera que llame a la función con un argumento real.
  2. Tiene la intención de dejar el argumento sin modificar, pero lo modificó accidentalmente . Eso es un error, arréglalo.
  3. Tiene la intención de modificar el argumento para usarlo dentro de la función, pero no esperaba que la modificación fuera visible fuera de la función. En ese caso, debe hacer una copia del argumento, ¡sea el predeterminado o no! Python no es un lenguaje de llamada por valor, por lo que no hace la copia por usted, debe ser explícito al respecto.

El ejemplo de la pregunta podría caer en la categoría 1 o 3. Es extraño que modifique la lista aprobada y la devuelva; deberías elegir uno u otro.

9 Norfeldt Jul 22 2013 at 14:35

¡Este "error" me dio muchas horas extras de trabajo! Pero estoy empezando a ver un uso potencial de él (pero me hubiera gustado que estuviera en el momento de la ejecución, todavía)

Les daré lo que veo como un ejemplo útil.

def example(errors=[]):
    # statements
    # Something went wrong
    mistake = True
    if mistake:
        tryToFixIt(errors)
        # Didn't work.. let's try again
        tryToFixItAnotherway(errors)
        # This time it worked
    return errors

def tryToFixIt(err):
    err.append('Attempt to fix it')

def tryToFixItAnotherway(err):
    err.append('Attempt to fix it by another way')

def main():
    for item in range(2):
        errors = example()
    print '\n'.join(errors)

main()

imprime lo siguiente

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
8 ytpillai May 26 2015 at 06:04

Simplemente cambie la función para que sea:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a
7 user2384994 Aug 22 2013 at 12:58

Creo que la respuesta a esta pregunta radica en cómo Python pasa datos a parámetros (pasan por valor o por referencia), no mutabilidad o cómo Python maneja la declaración "def".

Una breve introduccion. Primero, hay dos tipos de tipos de datos en Python, uno es un tipo de datos elemental simple, como números, y otro tipo de datos son objetos. En segundo lugar, al pasar datos a parámetros, Python pasa el tipo de datos elementales por valor, es decir, hace una copia local del valor a una variable local, pero pasa el objeto por referencia, es decir, punteros al objeto.

Admitiendo los dos puntos anteriores, expliquemos qué sucedió con el código Python. Es solo porque pasa por referencia para objetos, pero no tiene nada que ver con mutable / inmutable, o posiblemente el hecho de que la instrucción "def" se ejecuta solo una vez cuando está definida.

[] es un objeto, por lo que Python pasa la referencia de [] a a, es decir, aes solo un puntero a [] que se encuentra en la memoria como un objeto. Sin embargo, solo hay una copia de [] con muchas referencias. Para el primer foo (), la lista [] se cambia a 1 mediante el método append. Pero tenga en cuenta que solo hay una copia del objeto de lista y este objeto ahora se convierte en 1 . Al ejecutar el segundo foo (), lo que dice la página web de effbot (los elementos ya no se evalúan) es incorrecto. ase evalúa como el objeto de lista, aunque ahora el contenido del objeto es 1 . ¡Este es el efecto de pasar por referencia! El resultado de foo (3) se puede derivar fácilmente de la misma forma.

Para validar aún más mi respuesta, echemos un vistazo a dos códigos adicionales.

====== No. 2 ========

def foo(x, items=None):
    if items is None:
        items = []
    items.append(x)
    return items

foo(1)  #return [1]
foo(2)  #return [2]
foo(3)  #return [3]

[]es un objeto, también lo es None(el primero es mutable mientras que el segundo es inmutable. Pero la mutabilidad no tiene nada que ver con la pregunta). Ninguno está en algún lugar del espacio, pero sabemos que está allí y solo hay una copia de Ninguno allí. Entonces, cada vez que se invoca foo, los elementos se evalúan (a diferencia de alguna respuesta que solo se evalúa una vez) para que sea Ninguno, para ser claros, la referencia (o la dirección) de Ninguno. Luego, en foo, el elemento se cambia a [], es decir, apunta a otro objeto que tiene una dirección diferente.

====== No. 3 =======

def foo(x, items=[]):
    items.append(x)
    return items

foo(1)    # returns [1]
foo(2,[]) # returns [2]
foo(3)    # returns [1,3]

La invocación de foo (1) hace que los elementos apunten a un objeto de lista [] con una dirección, digamos, 11111111. El contenido de la lista se cambia a 1 en la función foo en la secuela, pero la dirección no se cambia, todavía 11111111 Entonces viene foo (2, []). Aunque el [] en foo (2, []) tiene el mismo contenido que el parámetro predeterminado [] al llamar a foo (1), ¡su dirección es diferente! Dado que proporcionamos el parámetro explícitamente, itemsdebe tomar la dirección de este nuevo [], digamos 2222222, y devolverlo después de realizar algún cambio. Ahora se ejecuta foo (3). dado que solo xse proporciona, los elementos deben volver a tomar su valor predeterminado. ¿Cuál es el valor predeterminado? Se establece al definir la función foo: el objeto de lista ubicado en 11111111. Por lo tanto, los elementos se evalúan como la dirección 11111111 que tiene un elemento 1. La lista ubicada en 2222222 también contiene un elemento 2, pero no está señalada por elementos. más. En consecuencia, un apéndice de 3 hará items[1,3].

De las explicaciones anteriores, podemos ver que la página web de effbot recomendada en la respuesta aceptada no dio una respuesta relevante a esta pregunta. Es más, creo que un punto de la página web de effbot está mal. Creo que el código con respecto a la UI.Button es correcto:

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

Cada botón puede contener una función de devolución de llamada distinta que mostrará un valor diferente de i. Puedo proporcionar un ejemplo para mostrar esto:

x=[]
for i in range(10):
    def callback():
        print(i)
    x.append(callback) 

If we execute x[7]() we'll get 7 as expected, and x[9]() will gives 9, another value of i.

6 MisterMiyagi Dec 15 2018 at 19:09

TLDR: Define-time defaults are consistent and strictly more expressive.


Defining a function affects two scopes: the defining scope containing the function, and the execution scope contained by the function. While it is pretty clear how blocks map to scopes, the question is where def <name>(<args=defaults>): belongs to:

...                           # defining scope
def name(parameter=default):  # ???
    ...                       # execution scope

The def name part must evaluate in the defining scope - we want name to be available there, after all. Evaluating the function only inside itself would make it inaccessible.

Since parameter is a constant name, we can "evaluate" it at the same time as def name. This also has the advantage it produces the function with a known signature as name(parameter=...):, instead of a bare name(...):.

Now, when to evaluate default?

Consistency already says "at definition": everything else of def <name>(<args=defaults>): is best evaluated at definition as well. Delaying parts of it would be the astonishing choice.

The two choices are not equivalent, either: If default is evaluated at definition time, it can still affect execution time. If default is evaluated at execution time, it cannot affect definition time. Choosing "at definition" allows expressing both cases, while choosing "at execution" can express only one:

def name(parameter=defined):  # set default at definition time
    ...

def name(parameter=default):     # delay default until execution time
    parameter = default if parameter is None else parameter
    ...
4 PrzemekD Jan 03 2019 at 14:38

Every other answer explains why this is actually a nice and desired behavior, or why you shouldn't be needing this anyway. Mine is for those stubborn ones who want to exercise their right to bend the language to their will, not the other way around.

We will "fix" this behavior with a decorator that will copy the default value instead of reusing the same instance for each positional argument left at its default value.

import inspect
from copy import copy

def sanify(function):
    def wrapper(*a, **kw):
        # store the default values
        defaults = inspect.getargspec(function).defaults # for python2
        # construct a new argument list
        new_args = []
        for i, arg in enumerate(defaults):
            # allow passing positional arguments
            if i in range(len(a)):
                new_args.append(a[i])
            else:
                # copy the value
                new_args.append(copy(arg))
        return function(*new_args, **kw)
    return wrapper

Now let's redefine our function using this decorator:

@sanify
def foo(a=[]):
    a.append(5)
    return a

foo() # '[5]'
foo() # '[5]' -- as desired

This is particularly neat for functions that take multiple arguments. Compare:

# the 'correct' approach
def bar(a=None, b=None, c=None):
    if a is None:
        a = []
    if b is None:
        b = []
    if c is None:
        c = []
    # finally do the actual work

with

# the nasty decorator hack
@sanify
def bar(a=[], b=[], c=[]):
    # wow, works right out of the box!

It's important to note that the above solution breaks if you try to use keyword args, like so:

foo(a=[4])

The decorator could be adjusted to allow for that, but we leave this as an exercise for the reader ;)