¡Descarga PYTHON NO MUERDE...... y más Diapositivas en PDF de Tecnología solo en Docsity!
Python no muerde Documentation
Versión 2.
Roberto Alsina (actualización por Martín Gait
05 de noviembre de 2017
II
Autor Roberto Alsina
Versión $Revision: 8e80f80bdea9 $
Índice general 1
CAPÍTULO 1
Introducción
1.1 Requisitos
Éste es un libro sobre Python^1. Es un libro que trata de explicar una manera posible de usarlo, una manera de tomar
una idea de tu cabeza y convertirla en un programa, que puedas usar y compartir.
¿Qué necesitás saber para poder leer este libro?
El libro no va a explicar la sintaxis de python, sino que va a asumir que la conocés. De todas formas, la primera vez
que aparezca algo nuevo, va a indicar dónde se puede aprender más sobre ello. Por ejemplo:
Creamos una lista con los cuadrados de los números pares
cuadrados = [ x**2 for x in numeros if x%2 == 0 ]
Referencia
Eso es una comprensión de lista
En general esas referencias van a llevarte al Tutorial de Python en castellano. Ese libro contiene toda la información
acerca del lenguaje que se necesita para poder seguir éste.
Cuando una aplicación requiera una interfaz gráfica, vamos a utilizar PyQt^2. No vamos a asumir ningún conocimiento
previo de PyQt pero tampoco se va a explicar en detalle, excepto cuando involucre un concepto nuevo.
Por ejemplo, no voy a explicar el significado de setEnabled^3 pero sí el concepto de signals y slots cuando haga
falta.
(^1) ¿Por qué Python? Porque es mi lenguaje favorito. ¿De qué otro lenguaje podría escribir? (^2) PyQt es software libre, es multiplataforma, y es muy potente y fácil de usar. Eso no quiere decir que las alternativas no tengan las mismas características, pero quiero enfocarme en programar, no en discutir, y yo prefiero PyQt. Si preferís una alternativa, este libro es libre: podés hacer una versión propia! (^3) PyQt tiene una excelente documentación de referencia para esas cosas.
1.2 Convenciones
Las variables, funciones y palabras reservadas de python se mostrarán en el texto con letra monoespaciada. Por ejem-
plo, for es una palabra reservada.
Los fragmentos de código fuente se va a mostrar así:
Creamos una lista con los cuadrados de los números impares
cuadrados = [ x**2 for x in numeros if x%2 > 0 ]
Los listados extensos o programas completos se incluirán sin cajas, mostrarán números de líneas e indicarán el nombre
del mismo:
titulo-listado
cuadrados.py
class listado
1 # Creamos una lista con los cuadrados de los números impares 2 cuadrados = [ x**2 for x in numeros if x%2 > 0 ]
En ese ejemplo, debería haber, en los ejemplos que acompañan al libro, un archivo codigo/X/cuadrados.py
donde X es el número del capítulo en el que el listado aparece.
1.3 Lenguaje
Las discusiones acerca de como escribir un libro técnico en castellano son eternas. Que en España se traduce todo todo
todo. Que en Argentina no. Que decir “cadena de caracteres” en lugar de string es malo para la ecología.
Por suerte en este libro hay un único criterio superador que ojalá otros libros adopten: Está escrito como escribo yo.
Ni un poquito distinto. No creo que siquiera califique como castellano, como mucho está escrito en argentino. Si a los
lectores de la ex madre patria les molesta el estilo... tradúzcanlo.
1.4 Mapa
Dentro de lo posible, voy a intentar que cada capítulo sea autocontenido, explicando un tema sin depender demasiado
de los otros, y terminando con un ejemplo concreto y funcional.
Éstos son los capítulos del libro, con breves descripciones.
1. Introducción
2. Pensar en python
Programar en python, a veces, no es como programar en otros lenguajes. Acá vas a ver algunos ejemplos. Si te
gustan... python es para vos. Si no te gustan... bueno, el libro es barato... capaz que Java es lo tuyo..
3. La vida es corta
Por eso, hay muchas cosas que no vale la pena hacer. Claro, yo estoy escribiendo un editor de textos así que este
capítulo es pura hipocresía...
4. Las capas de una aplicación
Batman, los alfajores santafesinos, el ozono... las mejores cosas tienen capas. Cómo organizar una aplicación en
capas.
4 Capítulo 1. Introducción
dependencias.graph.pdf
Figura 1.1: Este libro se lee siguiendo las flechas.
Nuestra misión en este capítulo es pensar en qué quiere decir Eby con “código python idiomático” en esa cita. Nunca
nadie va a poder hacer un pythonómetro que te mida cuán idiomático es un fragmento de código, pero es posible
desarrollar un instinto, una “nariz” para sentir el “olor a python”, así como un enófilo^1 aprende a distinguir el aroma a
clavos de hierro-níquel número 7 ligeramente oxidados en un Cabernet Sauvignon.^2
Y si la mejor forma de conocer el vino es tomar vino, la mejor forma de conocer el código es ver código. Este capítulo
no es exhaustivo, no muestra todas las maneras en que python es peculiar, ni todas las cosas que hacen que tu código
sea “pythonic” – entre otros motivos porque no las conozco – pero muestra varias. El resto es cuestión de gustos.
Get/Set
Una instancia de una clase contiene valores. ¿Cómo se accede a ellos? Hay dos maneras. Una es con “getters y setters”,
y estas son algunas de sus manifestaciones:
Un getter te "toma" (get) un valor de adentro de un objeto y
se puede ver así:
x1 = p.x() x1 = p.get_x() x1 = p.getX()
Un setter "mete" un valor en un objeto y puede verse así:
p.set_x(x1) p.setX(x1)
Otra manera es simplemente usar un miembro x de la clase:
p.x = x x1 = p.x
(^1) En mi barrio los llamábamos curdas. (^2) Con la esperanza de ser un poco menos pretencioso y/o chanta, si Zeus quiere.
6 Capítulo 1. Introducción
La ventaja de usar getters y setters es el “encapsulamiento”. No dicta que la clase tenga un miembro x, tal vez el valor
que yo ingreso via setX es manipulado, validado, almacenado en una base de datos, o tatuado en el estómago de
policías retirados con problemas neurológicos, lo único que importa es que luego cuando lo saco con el getter me dé
lo que tenga que dar (que no quiere decir “me dé lo mismo que puse”).
Muchas veces, los getters/setters se toman como un hecho de la vida, hago programación orientada a objetos => hago
getters/setters.
Bueno, no.
Analogía rebuscada
En un almacén, para tener un paquete de yerba, hay que pedírselo al almacenero. En un supermercado, para tener un
paquete de yerba, hay que agarrar un paquete de yerba. En una farmacia (de las grandes), para obtener un paquete de
yerba hay que agarrar un paquete de yerba, pero para tener un Lexotanil hay que pedirlo al farmacéutico.
En Java o C++, la costumbre es escribir programas como almacenes, porque la alternativa es escribir supermercados
donde chicos de 5 compran raticida.
En Python, la costumbre es escribir programas como supermercados, porque se pueden convertir en farmacias apenas
decidamos que tener raticida es buena idea.
Imaginemos que estamos escribiendo un programa que trabaja con “puntos” o sea coordenadas (X,Y), y que queremos
implementarlos con una clase. Por ejemplo:
titulo-listado
Listado 1
class listado
1 class Punto (object): 2 def init(self, x=0, y=0): 3 self.set_x(x) 4 self.set_y(y) 5 6 def x(self): 7 return self._x 8 9 def y(self): 10 return self._y 11 12 def set_x(self,x): 13 self._x=x 14 15 def set_y(self,y): 16 self._y=y
Esa es una implementación perfectamente respetable de un punto. Guarda X, guarda Y, permite volver a averiguar sus
valores... el problema es que eso no es python. Eso es C++. Claro, un compilador C++ se negaría a procesarlo, pero a
mí no me engañan tan fácil, eso es C++ reescrito para que parezca python.
¿Por qué eso no es python? Por el obvio abuso de los métodos de acceso (accessors, getter/setters), que son completa-
mente innecesarios.
Si la clase punto es simplemente esto, y nada más que esto, y no tiene otra funcionalidad, entonces prefiero esta:
titulo-listado
Listado 2
1.5. Acerca del Autor 7
4 def set_x(self, x): 5 self._x = abs(x)
Pero... también es fácil de hacer en el listado 2, sin cambiar la interfaz que se presenta al usuario:
titulo-listado
Listado 5
class listado
1 class PuntoDerecho (object): 2 '''Un punto que solo puede estar a la derecha del eje Y''' 3 4 def get_x(self): 5 return self._x 6 7 def set_x(self, x): 8 self._x = abs(x) 9 10 x = property(get_x, set_x)
Obviamente esto es casi lo mismo que si partimos del listado 1, pero con algunas diferencias:
La forma de acceder a x o de modificarlo es mejor – print p.x en lugar de print p.x(). Sí, es cuestión
de gustos nomás.
No se hicieron los métodos para y por ser innecesarios.
Esto es importante: de ser necesarios esos métodos en el futuro es fácil agregarlos. Si nunca lo son, entonces el
listado 1 tiene dos funciones inútiles.
Sí, son dos funciones cortas, que seguramente no crean bugs pero tienen implicaciones de performance, y tienen
un efecto que a mí personalmente me molesta: separan el código que hace algo metiendo en el medio código
que no hace nada.
Si esos métodos son funcionalmente nulos, cada vez que están en pantalla es como una franja negra de censura
de 5 líneas de alto cruzando mi editor. Es molesto.
Singletons
En un lenguaje funcional, uno no necesita patrones de diseño porque el lenguaje es de tan alto nivel que
terminás programando en conceptos que eliminan los patrones de diseño por completo.
—Slava Akhmechet
Una de las preguntas más frecuentes de novicios en python, pero con experiencia en otros lenguajes es “¿cómo hago
un singleton?”. Un singleton es una clase que sólo puede instanciarse una vez. De esa manera, uno puede obtener esa
única instancia simplemente reinstanciando la clase.
Hay varias maneras de hacer un singleton en python, pero antes de eso, dejemos en claro qué es un singleton: un
singleton es una variable global “lazy”.
En este contexto “lazy” quiere decir que hasta que la necesito no se instancia. Excepto por eso, no habría diferencias
visibles con una variable global.
El mecanismo “obvio” para hacer un singleton en python es un módulo, que son singletons porque así están imple-
mentados.
Ejemplo:
1.5. Acerca del Autor 9
>>> import os >>> os.x= >>> os.x 1 >>> import os as os >>> os2.x 1 >>> os2.x= >>> os.x 4 >>>
No importa cuantas veces importe os (o cualquier otro módulo), no importa con qué nombre lo haga, siempre es el
mismo objeto.
Por lo tanto, podríamos poner todos nuestros singletons en un módulo (o en varios) e instanciarlos con import y
funciones dentro de ese módulo.
Ejemplo:
titulo-listado
singleton1.py
class listado
>>> import singleton >>> uno=singleton1.misingle() >>> dos=singleton1.misingle() >>> print uno [] >>> uno.append('xx') >>> print dos ['xx']
Como pueden ver, uno y dos son el mismo objeto.
Una alternativa es no usar un singleton, sino lo que Alex Martelli llamó un Borg:
class Borg : __shared_state = {} def init(self): self.dict = self.__shared_state
¿Cómo funciona?
>>> a=Borg() >>> b=Borg() >>> a.x= >>> print b.x 1
Si bien a y b no son el mismo objeto por lo que no son realmente singletons, el efecto final es el mismo.
Por último, si andás con ganas de probar magia más potente, es posible hacer un singleton usando metaclases, según
esta receta de Andres Tuells:
1 ## {{{ http://code.activestate.com/recipes/102187/ (r1) 2 """ 3 USAGE:
10 Capítulo 1. Introducción
Repetirse es malo.
—Anónimo
Hay una estructura de control que Knuth llama el “loop n y medio” (n-and-half loop). Es algo así:
loop-n-y-medio.graph.pdf
Figura 1.2: ¡Se sale por el medio! Como siempre se pasa al menos por una parte del loop (A), Knuth le puso “loop n y
medio”.
Ésta es la representación de esta estructura en Python:
while True: frob(gargle)
Cortamos?
if gargle.blasted:
Cortamos!
break refrob(gargle)
No, no quiero que me discutan. Ésa es la forma de hacerlo. No hay que tenerle miedo al break! En particular la
siguiente forma me parece mucho peor:
frob(gargle)
Seguimos?
while not gargle.blasted: refrob(gargle) frob(gargle)
Es más propensa a errores. Antes, podía ser que frob(gargle) no fuera lo correcto. Ahora no solo puede ser
incorrecto, sino que puede ser incorrecto o inconsistente, si cambio solo una de las dos veces que se usa.
Claro, en un ejemplo de juguete esa repetición no molesta. En la vida real, tal vez haya 40 líneas entre una y otra y no
sea obvio que esa línea se repite.
Switches
Hay una cosa que muchas veces los que programan en Python envidian de otros lenguajes... switch (o case).
12 Capítulo 1. Introducción
Sí, Python no tiene un “if multirrama” ni un “goto computado” ni nada de eso. Pero ... hay maneras y maneras de
sobrevivir a esa carencia.
Esta es la peor:
if codigo == 'a': return procesa_a() if codigo == 'b': return procesa_b() : : etc.
Esta es apenas un cachito mejor:
if codigo == 'a': return procesa_a() elif codigo == 'b': return procesa_b() : : etc.
Esta es la buena:
procesos = { 'a': procesa_a, 'b': procesa_b, : : etc. }
return procesoscodigo
Al utilizar un diccionario para clasificar las funciones, es mucho más eficiente que una cadena de if. Es además
muchísimo más fácil de mantener (por ejemplo, podríamos poner procesos en un módulo separado).
Patos y Tipos
“Estás en un laberinto de pasajes retorcidos, todos iguales.”
—Will Crowther en “Adventure”
“Estás en un laberinto de pasajes retorcidos, todos distintos.”
—Don Woods en “Adventure”
Observemos este fragmento de código:
def diferencia(a,b):
Devuelve un conjunto con las cosas que están
en A pero no en B
return set(a) - set(b)
Set
1.5. Acerca del Autor 13
Genéricos
Supongamos que necesito poder crear listas con cantidades arbitrarias de objetos, todos del mismo tipo, inicializados
al mismo valor.
Comprensión de lista
En las funciones que siguen, [tipo() for i in range(cantidad)] se llama una comprensión de lista, y
es una forma más compacta de escribir un for para generar una lista a partir de otra:
resultado=[] for i in range(cantidad): resultado.append(tipo())
No conviene utilizarlo si la expresión es demasiado complicada.
Ver también: Listas por comprensión en el tutorial de Python
Un enfoque ingenuo podría ser este:
def listadestr(cantidad): return ['' for i in range(cantidad)]
def listadeint(cantidad): return [0 for i in range(cantidad)]
Y así para cada tipo que necesite...
Los defectos de esa solución son obvios. Una mejor solución:
def listadecosas(tipo, cantidad): return [tipo() for i in range(cantidad)]
Esa es una aplicación de programación genérica. Estamos creando código que solo puede tener un efecto cuando, más
adelante, lo apliquemos a un tipo. Es un caso extremo de lo mostrado anteriormente, en este caso literalmente el tipo
a usar no importa. ¡Cualquier tipo que se pueda instanciar sin argumentos sirve!
Desde ya que es posible – como diría un programador C++ – “especializar el template”:
def templatelistadecosas(tipo): def listadecosas(cantidad): return [tipo() for i in range(cantidad)] return listadecosas
>>> listadestr=templatelistadecosas(str) >>> listadeint=templatelistadecosas(int) >>> >>> listadestr(10) ['', '', '', '', '', '', '', '', '', ''] >>> listadeint(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
El truco de ese fragmento es que templatelistadecosas crea y devuelve una nueva función cada vez que la
invoco con un tipo específico. Esa función es la “especialización” de templatelistadecosas.
Otra forma de hacer lo mismo es utilizar la función functools.partial de la biblioteca standard:
1.5. Acerca del Autor 15
import functools def listadecosas(tipo, cantidad): return [tipo() for i in range(cantidad)]
listadestr=functools.partial(listadecosas, (str)) listadeint=functools.partial(listadecosas, (int))
Este enfoque para resolver el problema es más típico de la así llamada “programación funcional”, y partial es
una función de orden superior (higher-order function) que es una manera de decir que es una función que se aplica a
funciones.
¿Notaron que todo lo que estamos haciendo es crear funciones muy poco específicas?
Por ejemplo, listadecosas también puede hacer esto:
import random >>> listaderandom=functools.partial(listadecosas, ( lambda : random.randint(0,100))) >>> listaderandom(10) [68, 92, 83, 55, 89, 2, 9, 74, 9, 58]
Después de todo... ¿Quién dijo que tipo era un tipo de datos? ¡Todo lo que hago con tipo es tipo()!
O sea que tipo puede ser una clase, o una función, o cualquiera de las cosas que en python se llaman callables.
lambdas
lambda define una “función anónima”. EL ejemplo usado es el equivalente de
def f(): return random.randint(0,100) listaderandom=functools.partial(listadecosas, f)
La ventaja de utilizar lambda es que, si no se necesita reusar la función, mantiene la definición en el lugar donde se
usa y evita tener que buscarlo en otra parte al leer el código.
Más información
Decoradores
En un capítulo posterior vamos a ver fragmentos de código como este:
Esos misteriosos @algo son decoradores. Un decorador es simplemente una cosa que se llama pasando la función a
decorar como argumento. Lo que en matemática se denomina “composición de funciones”.
Usados con cuidado, los decoradores mejoran mucho la legibilidad de forma casi mágica. ¿Querés un ejemplo? Así se
vería ese código sin decoradores:
def alta(): """Crea un nuevo slug""" : :
UGH
alta = bottle.route('/')(bottle.view('usuario.tpl')(alta))
¿Cuándo usar decoradores? Cuando querés cambiar el comportamiento de una función, y el cambio es:
16 Capítulo 1. Introducción