lunes, 19 de mayo de 2014

Más sobre Python

Aparte de completar la entrada anterior sobre Python, incluyo aquí aquellas cosas que me han vuelto loco en su momento. En especial, las pequeñas GRANDES cosas que no se mencionan en ningún tutorial sobre Python y que he descubierto con sangre.

Conclusiones:
  1. Sigue siendo mi lenguaje preferido.
  2. El manejo de husos horarios en Python es la peor experiencia de programación de mi vida.
  3. Los tutoriales de iniciación obvian unas pocas cosas muy importantes al empezar a programar en serio.

if var

Es el "idiom" más recurrente de Python, y normalmente se explica como "si var tiene algo".

Si se aplica siempre está lógica, morirás en el intento, porque realmente significa:
"si var no es None y no es el valor por defecto para este tipo de variable".
Por tanto estas cosas son True:
  • bool('0') 
  • bool(' ') 
  • bool(-1)
  • bool([0])
Y estas cosas son False:
  • bool(0)
  • bool('')
  • bool(None)
  • bool([])
Ya que el valor por defecto para una cadena de texto es la cadena vacía, para un entero es el cero, para una lista es la lista vacía, etc.

Copia de colecciones

Cuando copiamos una lista o diccionario, estamos copiando en realidad la referencia al objeto, con lo cual si modificamos, el original, se modifica también la copia.
Para evitar esto hay que usar el método 'deepcopy' de la librería 'copy':
lista2 = copy.deepcopy(lista1)
En los casos en que nos hay colecciones anidadas, puede bastar una copia superficial (shallow), que puede hacer de estas formas con los diccionarios:
dicc2 = dict(dicc1)
dicc2 = dicc1.copy()
dicc2 = copy.copy(dicc1)
y de estas formas con las listas:
lista2 = list(lista1)
lista2 = copy.copy(lista1)

Paso de variables a funciones

En realidad sigo sin tener claro como funciona, pero sí puedo advertir de serios efectos colaterales si no se tienen en cuenta ciertas cosas.

En Python se utiliza el paso por valor de referencias a objetos , aunque los valores inmutables (enteros, tuplas...) se comportan como paso por valor. Esto provoca que al pasar un objeto mutable no siempre estemos modificando el objeto original. P.e. Estas dos expresiones son idénticas donde se define el diccionario, pero son completamente distintas si se utilizan sobre una referencia recibida:
dicc = {} # ésta no borra el diccionario "original"
dicc.clear() # ésta sí
En cuanto a las variables globales, se puede acceder directamente a ellas, para lectura, desde dentro de una función, siempre que no exista una variable local con su mismo nombre. Pero si intentamos modificar su valor, en realidad creará al vuelo una variable local con su mismo nombre. Para poder modificar una variable global debemos declarla así, dentro de la función:
global mi_variable_global
Nota: si la variable global es mutable (p.e. un diccionario) y no la declaramos con 'global' dentro de una función, no podremos hacer que apunte a otro objeto, pero sí podremos alteraTruer su contenido. Por tanto, nuevamente, dicc={} no borraría el diccionario original, pero dicc.clear(), sí, y dicc['campo1']=3 alteraría el contenido de la variable global.

Vaciar lista

Esto crea una lista vacía, pero no vacía una existente:
l = []
Para vaciar una existente no existe un método como el 'clear' de los diccionarios, sino que hay que hacer:
l[:] = [] # equivalente: del l[:]
No es muy fácil de recordar, pero como se intuye, mediante slicing podemos vaciar solo una parte de la lista:
l[2:3] = [] # equivalente: del l[2:3]

Operadores * y **

Ambos tienen sentido solo al pasar parámetros a una función. Hablaré de sucesión o secuencia de parámetros, ya que la palabra "lista" en Python es un tipo de datos. Para mí, la secuencia de parámetros es una cosa rara, que bien se podría asimilar a unos de los tipos o darle una entidad más explícita.

La secuencia de parámetros en la llamada a una función solo puede contener valores sin nombre y valores con nombre separados por comas. Estos últimos tienen que ir al final obligatoriamente, aunque el orden entre ellos no tiene por qué ser el mismo que en la definición de la función.

Al hilo de esto:
  • * convierte una lista en una sucesión de parámetros sin nombre
  • ** convierte un diccionario en una sucesión de parámetros con nombre
Estos operadores sirven para pasar a una función un número variable de parámetros. Cuidado con usar * con un diccionario, ya que el compilador no se quejará, pero por lo general no tiene sentido y su efecto puede ser aleatorio (al no ser los diccionarios ordenados).

Ordenar un diccionario

En Python 2.7 se introdujeron los SortedDict. Para Python 2.6 se puede usar una implementación completa, añadiendo una librería, o bien, en algunos casos, basta con usar este truco simple para recorrerlo ordenado (siendo d un diccionario):
for k in sorted(d):
    print d[k]

También:
  • sorted(d.iterkeys()) 
  • sorted(d.keys())

Doctest

Considero Doctest muy útil por ser una forma tremendamente compacta de implementar pruebas unitarias, pero tiene algún comportamiento extraño derivado del hecho de que la parte de comprobación, es una cadena de texto:
  • El valor que devuelve no siempre tiene la forma esperada, en el caso de un string, por lo que la cadena de comprobación puede tener que ser diferente a la que se visualiza.
  • No se puede poner ningún tipo de expresión en la cadena de comprobación, por lo que en estos casos, tenemos que evaluar una comparación contra dicha expresión y comprobar si devuelve True o False