apuntes - conjuntos en python 3
¿Por qué la adición de la lista de Python debe ser homogénea? (4)
¿Puede alguien familiarizado con las partes internas de Python (CPython u otras implementaciones) explicar por qué se requiere que la adición de la lista sea homogénea?
In [1]: x = [1]
In [2]: x+"foo"
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
C:/Users/Marcin/<ipython-input-2-94cd84126ddc> in <module>()
----> 1 x+"foo"
TypeError: can only concatenate list (not "str") to list
In [3]: x+="foo"
In [4]: x
Out[4]: [1, ''f'', ''o'', ''o'']
¿Por qué el x+"foo"
anterior no debería devolver el mismo valor que el valor final de x
en la transcripción anterior?
Esta pregunta se deriva de la pregunta de NPE aquí: ¿Está el comportamiento de la lista de Python + = iterable documentado en alguna parte?
Actualización: Sé que no se requiere que +=
heterogéneo funcione (pero lo hace) y, de la misma manera, no se requiere que +
sea heterogéneo un error. Esta pregunta es acerca de por qué se hizo esta última elección.
Es demasiado decir que los resultados de agregar una secuencia a una lista son inciertos. Si esa fuera una objeción suficiente, tendría sentido prevenir un +=
heterogéneo. Actualización 2: en particular, python siempre delega las llamadas del operador al operando izquierdo, por lo que no surge el problema "qué es lo correcto" surge ": el objeto de la izquierda siempre rige (a menos que delegue a la derecha).
Actualización 3: Para cualquier persona que arguya que esta es una decisión de diseño, explique (a) por qué no está documentada; o (b) donde esté documentado.
Actualización 4: "¿qué debería devolver [1] + (2, )
?" Debe devolver un valor de resultado igual al valor de una variable x
inicialmente mantiene [1]
inmediatamente después de x+=(2, )
. Este resultado está bien definido.
Creo que los diseñadores de Python hicieron una adición de esta manera para que el operador ''+'' permanezca constantemente conmutativo con respecto al tipo de resultado: type(a + b) == type(b + a)
Todos esperan que 1 + 2 tenga el mismo resultado que 2 + 1. ¿Esperarías que [1] + ''foo'' sea igual que ''foo'' + [1]? En caso afirmativo, ¿cuál debería ser el resultado?
Tiene 3 opciones, puede elegir el operando izquierdo como tipo de resultado, el operando derecho como tipo de resultado o generar un error.
+ = no es conmutativo porque contiene la asignación. En este caso, elija el operando izquierdo como tipo de resultado o lance. La sorpresa aquí es que a += b
no es lo mismo que a = a + b
. a += b
no se traduce en inglés a "Agregar a a b y asignar el resultado a a". Se traduce en "Agregar a a b en su lugar". Es por eso que no funciona en inmutables como cuerdas o tuplas.
Gracias por los comentarios. Editado el post.
Desde el Zen de Python:
Ante la ambigüedad, rechace la tentación de adivinar.
Veamos lo que pasa aquí:
x + y
Esto nos da un valor, pero ¿de qué tipo? Cuando agregamos cosas en la vida real, esperamos que el tipo sea el mismo que el tipo de entrada, pero ¿qué pasa si son dispares? Bueno, en el mundo real, nos negamos a agregar 1
y "a"
, no tiene sentido.
¿Qué pasa si tenemos tipos similares? En el mundo real, nos fijamos en el contexto. La computadora no puede hacer esto, así que tiene que adivinar. Python elige el operando izquierdo y deja que decida. Su problema se produce debido a esta falta de contexto.
Digamos que un programador quiere hacer ["a"] + "bc"
; esto podría significar que quieren "abc"
o ["a", "b", "c"]
. Actualmente, la solución es llamar a "".join()
en el primer operando o list()
en el segundo, lo que le permite al programador hacer lo que quiere y es claro y explícito.
Su sugerencia es que Python adivine (al tener una regla incorporada para elegir un operando determinado), para que el programador pueda hacer lo mismo al hacer la adición, ¿por qué es mejor? Simplemente significa que es más fácil obtener el tipo incorrecto por error, y debemos recordar una regla arbitraria (tipo de selección de operandos izquierdo). En su lugar, recibimos un error para que podamos darle a Python la información que necesita para hacer la llamada correcta.
Entonces, ¿por qué es +=
diferente? Bueno, eso es porque le estamos dando a Python ese contexto. Con la operación en el lugar le estamos diciendo a Python que modifique un valor, por lo que sabemos que estamos tratando con algo del tipo que el valor que estamos modificando es. Este es el contexto que Python necesita para hacer la llamada correcta, por lo que no necesitamos adivinar.
Cuando hablo de adivinar, estoy hablando de que Python adivine la intención del programador. Esto es algo que Python hace mucho, ver división en 3.x. /
hace división flotante, corrigiendo el error de ser división entera en 2.x.
Esto se debe a que implícitamente estamos pidiendo una división flotante cuando intentamos dividir. Python tiene esto en cuenta y sus operaciones se realizan de acuerdo con eso. Del mismo modo, se trata de adivinar la intención aquí. Cuando agregamos con +
nuestra intención no está clara. Cuando usamos +=
, es muy claro.
Estos informes de errores sugieren que esta peculiaridad de diseño fue un error.
Sí, este es el comportamiento esperado y sí, es inconsistente.
Ha sido así durante mucho tiempo y Guido dijo que no volvería a hacerlo (está en su lista de arrepentimientos). Sin embargo, no vamos a descifrar el código cambiándolo (
list.__iadd__
funciona comolist.extend
).
La intención era que la
list.__iadd__
correspondiera exactamente alist.extend()
. No hay necesidad de hipergeneralizar lalist.__add__()
también: es una función que las personas que no quieren sorprenderse con los ejemplos de Martin pueden evitarlos mediante el uso de+
llano para las listas.
(Por supuesto, hay algunos de nosotros que encontramos este comportamiento bastante sorprendente, incluido el desarrollador que abrió el informe de errores).
(Gracias a @Mouad por encontrarlos).
Mi conjetura es que Python está fuertemente tipado, y no hay una clara indicación de lo que hay que hacer aquí. ¿Le está pidiendo a Python que agregue la cadena en sí, o que la coloque en una lista (que es lo que usted indicó que le gustaría que hiciera)?
Recordar explícito es mejor que implícito. En el caso más común, ninguna de esas suposiciones es correcta y está intentando accidentalmente hacer algo que no intentó. Aumentar un TypeError y dejar que usted lo resuelva es la cosa más segura y más pitónica que puede hacer aquí.