[vc_row][vc_column][vc_column_text]Nunca es fácil admitir cuando haces las cosas mal, pero cometer errores es parte de cualquier proceso de aprendizaje, desde aprender a caminar hasta aprender un nuevo lenguaje de programación, como Python.
Aquí hay una lista de tres cosas que debe evitar al aprender a programar en Python, presentado para que los programadores más nuevos de Python puedan evitar cometer estos errores.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
1. Tipos de datos mutables como argumentos predeterminados en la definición de funciones
Tiene sentido, ¿verdad? Tiene una pequeña función que, digamos, busca enlaces en una página actual y opcionalmente la agrega a otra lista suministrada.
1 2 3 4 | def search_for_links(page, add_to=[]): new_links = page.search_for_links() add_to.extend(new_links) return add_to |
A primera vista, parece Python perfectamente normal, y de hecho lo es. Funciona. Pero hay problemas con eso. Si proporcionamos una lista para el parámetro add_to, funciona como se esperaba. Sin embargo, si dejamos que use el valor predeterminado, sucede algo interesante.
Pruebe el siguiente código:
1 2 3 4 5 6 7 | def fn(var1, var2=[]): var2.append(var1) print var2 fn(3) fn(4) fn(5) |
Tu esperas algo como:
1 2 3 | [3] [4] [5] |
Pero lo que vemos, es algo como:
1 2 3 | [3] [3, 4] [3, 4, 5] |
¿Por qué? Bueno, verá, la misma lista se usa cada vez. En Python, cuando escribimos la función de esta manera, la lista se instancia como parte de la definición de la función. No se crea una instancia cada vez que se ejecuta la función. Esto significa que la función sigue usando exactamente el mismo objeto de la lista una y otra vez, a menos que, por supuesto, proporcionemos otro:
1 | fn(3, [4]) |
Tal como se esperaba La forma correcta de lograr el resultado deseado es:
1 2 3 4 | def fn(var1, var2=None): if not var2: var2 = [] var2.append(var1) |
O en nuestro primer ejemplo:
1 2 3 4 5 6 | def search_for_links(page, add_to=None): if not add_to: add_to = [] new_links = page.search_for_links() add_to.extend(new_links) return add_to |
Esto mueve la creación de instancias del tiempo de carga del módulo para que ocurra cada vez que se ejecuta la función. Tenga en cuenta que para tipos de datos inmutables, como tuplas, cadenas o ints, esto no es necesario. Eso significa que está perfectamente bien hacer algo como:
1 2 | def func(message="my message"): print message |
[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
2. Tipos de datos mutables como variables de clase
El siguiente error es muy similar al anterior. Considera lo siguiente:
1 2 3 4 5 | class URLCatcher(object): urls = [] def add_url(self, url): self.urls.append(url) |
Este código se ve perfectamente normal. Tenemos un objeto con un almacenamiento de URL. Cuando llamamos al método add_url, agrega una URL determinada a la tienda. Perfecto ¿verdad? Veámoslo en acción:
1 2 3 4 | a = URLCatcher() a.add_url('http://www.google.') b = URLCatcher() b.add_url('http://www.bbc.co.') |
Donde tendríamos:
1 2 3 4 5 | b.urls ['http://www.google.com', 'http://www.bbc.co.uk'] a.urls ['http://www.google.com', 'http://www.bbc.co.uk'] |
No esperábamos eso. Instanciamos dos objetos separados, a y b. A recibió una URL y b la otra. ¿Cómo es que ambos objetos tienen ambas URL?
Resulta que es un poco el mismo problema que en el primer ejemplo. La lista de URL se crea una instancia cuando se crea la definición de clase. Todas las instancias de esa clase usan la misma lista. Ahora bien, hay algunos casos en que esto es ventajoso, pero la mayoría de las veces no quiere hacer esto. Desea que cada objeto tenga una tienda separada. Para hacer eso, modificaríamos el código como:
1 2 3 4 5 6 | class URLCatcher(object): def __init__(self): self.urls = [] def add_url(self, url): self.urls.append(url) |
Ahora la lista de URL se crea una instancia cuando se crea el objeto. Cuando instanciamos dos objetos separados, usarán dos listas separadas.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_column_text]
3. Errores de asignación mutable
Cambiemos un poco los engranajes y usemos otro tipo de datos mutable, el dict.
1 | a = {'1': "one", '2': 'two'} |
Ahora supongamos que queremos tomar ese dict y usarlo en otro lugar, dejando el original intacto.
1 2 3 | b = a b['3'] = 'three' |
Simple, verdad.
Ahora veamos nuestro dict original, a, el que no queríamos modificar:
1 | {'1': "one", '2': 'two', '3': 'three'} |
¿Por que se ve «b»?
Pero … retrocedamos y veamos qué pasa con nuestros otros tipos inmutables, una tupla por ejemplo:
1 2 3 | c = (2, 3) d = c d = (4, 5) |
Ahora c es: (2,3)
Mientras d es: (4,5)
Eso funciona como se esperaba. Entonces, ¿qué pasó en nuestro ejemplo? Al usar tipos mutables, obtenemos algo que se comporta un poco más como un puntero de C. Cuando dijimos b = a en el código anterior, lo que realmente queríamos decir era: b ahora también es una referencia a «a». Ambos señalan el mismo objeto en la memoria de Python. ¿Suena familiar? Eso es porque es similar a los problemas anteriores. De hecho, esta publicación realmente debería haber sido llamada, «El problema con mutables».
¿Pasa lo mismo con las listas? Sí. Entonces, ¿cómo lo solucionamos? Bueno, tenemos que ser muy cuidadosos. Si realmente necesitamos copiar una lista para procesarla, podemos hacerlo de la siguiente manera:
1 | b = a[:] |
Esto pasará y copiará una referencia a cada elemento en la lista y lo colocará en una nueva lista. Pero tenga cuidado: si cualquier objeto en la lista es mutable, volveremos a obtener referencias a esos, en lugar de copias completas.
Los dicts funcionan de la misma manera, y puedes crear esta copia costosa haciendo:
1 | b = a.copy() |
De nuevo, esto solo creará un nuevo diccionario que apunte a las mismas entradas que estaban presentes en el original. Por lo tanto, si tenemos dos listas que son idénticas y modificamos un objeto mutable apuntado por una clave de dict ‘a’, el objeto dict presente en dict ‘b’ también verá esos cambios.
El problema con los tipos de datos mutables es que son poderosos. Ninguno de los anteriores son problemas reales; son cosas a tener en cuenta para evitar problemas. Las costosas operaciones de copia presentadas como soluciones en el tercer artículo son innecesarias el 99% del tiempo. Su programa puede y probablemente debería modificarse para que esas copias ni siquiera sean necesarias en primer lugar.
Este artículo esta basado en 3 mistakes to avoid when learning to code in Python[/vc_column_text][/vc_column][/vc_row]