Skip to content

Instantly share code, notes, and snippets.

@chemacortes
Last active December 15, 2015 01:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chemacortes/5177340 to your computer and use it in GitHub Desktop.
Save chemacortes/5177340 to your computer and use it in GitHub Desktop.
Mutabilidad de listas. Un artículo de ch3m4.org.
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": "Mutabilidad de Listas"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<style>.rendered_html {font-size: 120%; font-family: Georgia, serif}</style>"
]
},
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Mutabilidad de Listas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Mucha gente, cuando se enfrenta por primera vez al lenguaje python, no entiende bien el concepto de *\"inmutabilidad\"* que tanto repite la documentaci\u00f3n al tratar de diferenciar algunos tipos contenedores como tuplas, listas, conjuntos y diccionarios.\n",
"\n",
"Por lo general, la gente formada en lenguajes de programaci\u00f3n cl\u00e1sicos tiene la idea de que las variables son porciones de memoria donde colocar valores. Que una variable no se \u00e9so, *variable*, resulta un contrasentido. Han visto *constantes*, pero s\u00f3lo sirven para inicializar variables y poco m\u00e1s. Si en su carrera hubieran sido formados en alg\u00fan lenguaje funcional se dar\u00edan cuenta que hay quienes piensan que las variables que cambian de valor son las raras, que lo m\u00e1s natural es que una variable conserve su valor inicial, o sea, que sea inmutable.\n",
"\n",
"Por poner un ejemplo, el siguiente c\u00f3digo est\u00e1 basado en una pregunta reciente en la lista [python-es][1]. Tenemos una lista de pares y queremos quitar las parejas repetidas con orden cambiado:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def quitar_dup(lista):\n",
"\n",
" for item in lista:\n",
"\n",
" item.reverse()\n",
"\n",
" if item in lista:\n",
" lista.remove(item)\n",
"\n",
" return lista\n",
"\n",
"L=[[1,2],[1,3],[2,1],[3,1]]\n",
"\n",
"print quitar_dup(L) #res: [[1, 3], [3, 1]]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"[[1, 3], [3, 1]]\n"
]
}
],
"prompt_number": 51
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A simple vista, el c\u00f3digo parece correcto, pero tenemos dos operaciones que pueden mutar listas: `.reverse()` y `.remove()`. De hecho, el resultado es incorrecto: `[[1, 3], [3, 1]]`\n",
"\n",
"A medida que recorremos la lista en el bucle `for`, la lista se va modificando, lo que da lugar a resultados inesperados. Si no lo ves bien, basta a\u00f1adir algunos `prints` en lugares estrat\u00e9gicos para que comprobar lo que pasa. De hecho, s\u00f3lo existen dos iteraciones para cuatro elementos que tiene la lista.\n",
"\n",
"Otro tipo de casos son cuando pasamos listas a funciones:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> def add(a, l):\n",
" if a not in l:\n",
" l+=[a]\n",
" return l \n",
" \n",
">>> L=[1,2,3]\n",
">>> add(1,L)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 36,
"text": [
"[1, 2, 3]"
]
}
],
"prompt_number": 36
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> add(4,L)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 37,
"text": [
"[1, 2, 3, 4]"
]
}
],
"prompt_number": 37
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> L"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 38,
"text": [
"[1, 2, 3, 4]"
]
}
],
"prompt_number": 38
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como efecto colateral, la funci\u00f3n ha modificado la lista pasada como argumento, algo que no es siempre deseable. El problema se agrava m\u00e1s si empleamos listas en valores por defecto:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> def add(a, l=[]):\n",
" if a not in l:\n",
" l+=[a]\n",
" return l\n",
">>> add(1)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 39,
"text": [
"[1]"
]
}
],
"prompt_number": 39
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> add(2)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 40,
"text": [
"[1, 2]"
]
}
],
"prompt_number": 40
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> add(3,[])"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 41,
"text": [
"[3]"
]
}
],
"prompt_number": 41
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> add(4)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 42,
"text": [
"[1, 2, 4]"
]
}
],
"prompt_number": 42
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Como se puede ver, aunque intentemos *resetear* el valor por defecto, la funci\u00f3n tiene un efecto memoria que es imposible de eliminar. Este efecto es a veces buscado, pero en general debe ser siempre evitado ya que desvirt\u00faa el sentido que tiene dar valores por defecto.\n",
"\n",
"Estos efectos son todav\u00eda m\u00e1s perniciosos con la *funciones lambda*. Al carecer de una *clausura* como las funciones, la evaluaci\u00f3n de una funci\u00f3n lambda depende del *scope* donde han sido definidas. Por ejemplo, observa esta creaci\u00f3n de una lista de funciones:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"fns=[]\n",
"\n",
"for i in range(5):\n",
" fns.append( lambda x: x+i)\n",
"\n",
"print fns[1](10)\n",
"print fns[2](10)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"14\n",
"14\n"
]
}
],
"prompt_number": 43
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Siempre a\u00f1ade `4` al argumento, que es el valor de `i` al acabar el bucle, independientemente de qu\u00e9 valor ten\u00eda esta variable en el momento de crear la funci\u00f3n lambda. No es de extra\u00f1ar que se recomiende dejar de usar estas funciones.\n",
"\n",
"Por \u00faltimo, otro efecto funesto de la mutabilidad de las listas aparece en la creaci\u00f3n de *listas multidimensionales* (aka *matrices*). Una forma r\u00e1pida de crear una matriz de 2x2 es: `[[0]*2]*2`. El problema aqu\u00ed est\u00e1 en que cuando clonamos listas, en lugar de copiar los elementos, los enlaza entre s\u00ed. Quiz\u00e1s se vea mejor si hacemos alguna operaci\u00f3n:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> l=[[0]*2]*2\n",
">>> l[0][0]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 44,
"text": [
"0"
]
}
],
"prompt_number": 44
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> l[0][0]=1\n",
">>> l"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 45,
"text": [
"[[1, 0], [1, 0]]"
]
}
],
"prompt_number": 45
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> l[0] is l[1]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 46,
"text": [
"True"
]
}
],
"prompt_number": 46
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Los elementos `l[0]` y `l[1]` son el mismo elemento. Que los elementos de una lista puedan estar *entrelazados* resulta muy interante para algunos algoritmos de b\u00fasquedas. Pero hay que conocer bien lo que estamos haciendo si no queremos llevarnos alguna sorpresa.\n"
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Recomendaciones para hacer c\u00f3digo funcional"
]
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Copia de listas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En funciones y m\u00e9todos, si recibimos una lista como argumento, la primera acci\u00f3n defensiva que deber\u00edamos hacer es copiar la lista en una variable local y trabajar solo con la variable local desde ese momento. Con una asignaci\u00f3n directa no se realiza una copia, m\u00e1s bien estar\u00edamos *enlazando* una nueva referenciasin solucionar nada.\n",
"\n",
"La forma consensuada entre programadores python de copiar una lista es con la operaci\u00f3n de *spliting* `L[:]`, aunque sirven otras operaciones idempotentes como `L*1` \u00f3 `L+[]`[^1]. Para listas de elementos entrelazados tendremos que acudir a otros mecanismos de copia como los que ofrece el [m\u00f3dulo `copy`][1], aunque no ser\u00e1 frecuente que lo necesitemos.\n",
"\n",
"[1]: http://docs.python.org/3.3/library/copy.html \"M\u00f3dulo copy\""
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def add(a, lista):\n",
" l=lista[:]\n",
" if a not in l:\n",
" l+=[a]\n",
" return l"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 47
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En cuanto a los argumentos por defecto, lo mejor es no usar nunca una lista para tal cosa. Una buena estrategia defensiva consiste en usar `None` de esta forma:\n"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def add(a, lista=None):\n",
" l = [] if lista is None else lista[:]\n",
" if a not in l:\n",
" l+=[a]\n",
" return l\n"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 48
},
{
"cell_type": "heading",
"level": 3,
"metadata": {},
"source": [
"Operaciones inmutables con listas"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"En cuanto a evitar las operaciones que mutan listas, siempre hay alternativas inmutables de todas estas operaciones. El siguiente cuadro puede servir como referencia:\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<p><style type=\"text/css\">\n",
"table {\n",
" color:#333333;\n",
" border-width: 1px;\n",
" border-color: #666666;\n",
" border-collapse: collapse;\n",
" font-family: \"New Courier\", monospaced, fixed;\n",
" font-size:14px;\n",
"}\n",
"table th {\n",
" border-width: 1px;\n",
" padding: 8px;\n",
" border-style: solid;\n",
" border-color: #666666;\n",
" background-color: #dedede;\n",
"}\n",
"table td {\n",
" border-width: 1px;\n",
" padding: 8px;\n",
" border-style: solid;\n",
" border-color: #666666;\n",
" background-color: #ffffff;\n",
"}\n",
"</style></p>\n",
"\n",
"<table>\n",
"<thead>\n",
"<tr>\n",
" <th>Mutable</th>\n",
" <th>Inmutable</th>\n",
"</tr>\n",
"</thead>\n",
"<tbody>\n",
"<tr>\n",
" <td><code>L.append(item)</code></td>\n",
" <td><code>L+[item]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>L.extend(sequence)</code></td>\n",
" <td><code>L + list(sequence)</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>L.insert(index, item)</code></td>\n",
" <td><code>L[:index] + [item] + L[index:]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>L.reverse()</code></td>\n",
" <td><code>L[::-1]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>L.sort()</code></td>\n",
" <td><code>sorted(L)</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>item = L.pop()</code></td>\n",
" <td><code>item,L = L[-1],L[:-1]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>item = L.pop(0)</code></td>\n",
" <td><code>item,L = L[0],L[1:]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>item = L.pop(index)</code></td>\n",
" <td><code>item, L = L[item], L[:item]+L[item+1:]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>L.remove(item)</code></td>\n",
" <td><code>L=L[:item]+L[item+1:]</code></td>\n",
"</tr>\n",
"<tr>\n",
" <td><code>L[i:j] = K</code></td>\n",
" <td><code>L[:i] + K + L[j:]</code></td>\n",
"</tr>\n",
"</tbody>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A la hora de decidir qu\u00e9 versi\u00f3n usar, la versi\u00f3n inmutable es m\u00e1s apropiada para programaci\u00f3n funcional y resulta incluos m\u00e1s intuitiva de interpretar. No es extra\u00f1o ver errores de c\u00f3digo donde se espera resultados de las operaciones `.sort()` o `.reverse()`, que siempre devuelven `None`. Para el int\u00e9rprete de python no hay error, pero a veces nos ser\u00e1 dif\u00edcil darnos cuenta de estos errores:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> #MODO ERR\u00d3NEO: machacamos la lista con None\n",
">>> l = [3,5,1,2,4]\n",
">>> l_2 = [x*x for x in l.sort()]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "'NoneType' object is not iterable",
"output_type": "pyerr",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-49-066b4c6b2c2c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;31m#MODO ERR\u00d3NEO: machacamos la lista con None\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0ml\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m4\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0ml_2\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0ml\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msort\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: 'NoneType' object is not iterable"
]
}
],
"prompt_number": 49
},
{
"cell_type": "code",
"collapsed": false,
"input": [
">>> #MODO CORRECTO\n",
">>> l = [3,5,1,2,4]\n",
">>> l_2 = [x*x for x in sorted(l)]\n",
">>> l_2\n"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "pyout",
"prompt_number": 50,
"text": [
"[1, 4, 9, 16, 25]"
]
}
],
"prompt_number": 50
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment