Introducción a la Computación Python avanzado Maximiliano Geier Facultad de Ciencias Exactas y Naturales, UBA 24/06/2015 Maximiliano Geier (UBA) Python avanzado 24/06/2015 1 / 32 Programando a lo Python Cada lenguaje de programación tiene múltiples formas de escribir las mismas cosas. Recursión for, while Clases Estructuras de datos Ciertas construcciones son más comunes que otras, se habla de “idioms”. En Python el “código idiomático” se lo conoce como pythónico1 . Lo que se intenta es mejorar la legibilidad del código. No necesariamente más lı́neas (ni menos) implica más legible, el criterio depende mucho del lenguaje. Vamos a ver algunos ejemplos de “idioms” en Python (y otros truquitos varios). 1 http://docs.python-guide.org/en/latest/writing/style/ Maximiliano Geier (UBA) Python avanzado 24/06/2015 2 / 32 Manejo de strings “tradicional” Python es muy cómodo para procesar cadenas de caracteres. Algunos ejemplos de cosas que se pueden hacer: Construir una lista separando una cadena por algún separador: >>> ’e.jem.plo’.split(’.’) [’e’, ’jem’, ’plo’] Volver a armarla: >>> ’-’.join(’e.jem.plo’.split(’.’)) ’e-jem-plo’ Particionar por un separador: >>> ’primero:segundo:tercero’.partition(’:’) (’primero’, ’:’, ’segundo:tercero’) Separar el nombre de un archivo: >>> ’/home/mgeier/laberinto.txt’.rpartition(’/’) (’/home/mgeier’, ’/’, ’laberinto.txt’) Maximiliano Geier (UBA) Python avanzado 24/06/2015 3 / 32 Manejo de strings “tradicional” (continuación) Reemplazar texto: >>> ’sarasa’.replace(’s’, ’z’) ’zaraza’ Prefijo: >>> ’preprefijo’.startswith(’pre’) True >>> ’preprefijo’.startswith(’prefi’) False Eliminar un final de lı́nea: >>> ’ejemplo\n’.rstrip(’\n’) ’ejemplo’ Espacios delante y detrás: >>> ’ muchos espacios ’muchos espacios’ Maximiliano Geier (UBA) ’.strip() Python avanzado 24/06/2015 4 / 32 Context managers Podemos separar en un bloque con with el código que hace uso de un recurso, como ser un archivo. with open(’ejemplo_tp.txt’, ’r’) as f: dim = f.readline() cargarDimension(dim) laberinto = f.readlines() cargarLaberinto(laberinto) Al salir del bloque with, el archivo f quedó cerrado (no hace falta llamar a f.close() explı́citamente). Podemos definir nuestros propios context managers en una clase: __enter__(self) __end__(self) Los objetos que definen sus propios context managers son en general los que modelan recursos que se piden y liberan (un archivo, una conexión de red, etc). Maximiliano Geier (UBA) Python avanzado 24/06/2015 5 / 32 Listas por comprensión Es una construcción que permite aplicar una función a cada elemento de una lista y almacenar su resultado en una lista nueva todo en una sola lı́nea. a = [fn(x) for x in lista] Esto es equivalente al siguiente código: a = [] for x in lista: a.append(fn(x)) Un uso interesante es “anidarlas”: with open(’tp_ej1.txt’, ’r’) as f: puntos = [tuple(float(y) for y in x.split()) for x in f] O filtrar elementos (“los x de lista tales que sean pares”): pares = [x for x in lista if x % 2 == 0] Ojo con la legibilidad: a = [(float(x), float(y)) for x, y in [(’{:.4f}’.format( \ random.random()*10), ’{:.4f}’.format( \ random.random()*10)) for _ in range(10)]] Maximiliano Geier (UBA) Python avanzado 24/06/2015 6 / 32 Regular expressions Regular expressions (o regexp) es la forma moderna de procesar cadenas de caracteres. Usamos un lenguaje muy sucinto para describir patrones en las cadenas de caracteres. Cada lenguaje de programación tiene su variante de lenguaje para describir expresiones regulares, pero son bastante parecidos entre sı́. En Python esto está implementado en el módulo re. Podemos dar una definición recursiva de expresiones regulares: Un sı́mbolo (que puede ser una letra, número o signo) es una expresión regular. Los caracteres especiales se escapan con una barra invertida (\). Los sı́mbolos ^ y $ corresponden a principio y final de lı́nea respectivamente. El punto . es una expresión regular que corresponde a cualquier caracter. [c1 c2 c3 ] es una expresión regular (“cualquier caracter que sea c1 , c2 o c3 ”). \d ≡ [0-9] ≡ [0123456789] (cualquier dı́gito decimal). [^\d] corresponde a cualquier caracter que no sea un dı́gito decimal. \w ≡ [a-zA-Z] (cualquier letra). Si a y b son expresiones regulares, ab es una expresión regular. (“a y después b”). a|b es una expresión regular (“a o b”). a* es una expresión regular (“a repetido 0 o más veces”). a+ es una expresión regular (“a repetido 1 o más veces”). Podemos capturar en grupos partes del match usando los paréntesis. Maximiliano Geier (UBA) Python avanzado 24/06/2015 7 / 32 Regular expressions (ejemplos) Para procesar un timestamp: >>> re.match(’(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})’, \ ... ’2015-05-06T10:56:04’).groups() (’2015’, ’05’, ’06’, ’10’, ’56’, ’04’) Para procesar el laberinto: with open(’laberinto.txt’, ’r’) as f: dim = [int(x) for x in re.match(’Dim\((\d+),(\d+)\)’, \ f.readline()).groups()] # dim = [4, 4] grid = [] for linea in f: grid.append([[bool(int(y)) for y in x.split(’,’)] for x in \ [x for x in re.split(’[\[\]]’, linea.rstrip(’\n’)) \ if len(x) > 0]]) # grid[0] = [[True,True,False,False],[False,True,True,False], \ # [True,True,False,True],[False,True,False,True]] Buscar todos los números decimales truncando hasta un dı́gito fraccionario: >>> s = ’{2, [4, 5]; 6; {3.1 -0.344}}’ >>> re.findall(’([^\d]|^)((\-|)\d+(\.\d|))’, s) [(’{’,’2’,’’,’’),(’[’,’4’,’’,’’),(’ ’,’5’,’’,’’),(’ ’,’6’,’’,’’), (’{’,’3.1’,’’,’.1’),(’ ’,’-0.3’,’-’,’.3’)] Maximiliano Geier (UBA) Python avanzado 24/06/2015 8 / 32 Ordenamiento Python incluye un algoritmo de ordenamiento que es una variante del mergesort. Para usarlo existen dos formas: Sobre la misma lista (in place): >>> a = [1,5,7,23,0,-4,3,7,4,4,2] >>> a.sort() >>> a [-4, 0, 1, 2, 3, 4, 4, 5, 7, 7, 23] En una lista nueva: >>> a = [1,5,7,23,0,-4,3,7,4,4,2] >>> b = sorted(a) >>> b [-4, 0, 1, 2, 3, 4, 4, 5, 7, 7, 23] >>> a [1, 5, 7, 23, 0, -4, 3, 7, 4, 4, 2] Maximiliano Geier (UBA) Python avanzado 24/06/2015 9 / 32 Formas de iterar listas Con range (iterar los ı́ndices): for i in range(len(a)): print(a[i]) Con in lista (iterar los elementos): for x in a: print(x) Esta forma funciona con cualquier “iterable” (cualquier estructura que uno pueda recorrer) Se puede definir cómo funciona el in para tipos propios definiendo el método especial __iter__(self) enumerate (ı́ndices y elementos al mismo tiempo): for i, x in enumerate(a): print(i, x) Dos (o más) listas al mismo tiempo (ojo con las longitudes de a y de b): for x, y in zip(a, b): print(x, y) Maximiliano Geier (UBA) Python avanzado 24/06/2015 10 / 32 Definiendo nuestro iter Recorrer filas y columnas en un tablero de ajedrez: class Celda(object): def __init__(self): self.c = False class Tablero(object): def __init__(self, n): self.tab = [[Celda() for _ in range(n)] for _ in range(n)] def __iter__(self): for f in self.tab: for c in f: yield c Luego, recorremos con for celda in Tablero(n). Árbol Binario: class ArbolBinario(object): ... def __iter__(self): for r in self.inorder(): yield r Maximiliano Geier (UBA) Python avanzado 24/06/2015 11 / 32 Otras formas de iterar Recorrer en orden inverso: >>> for x in reversed(a): ... print(x, end=’ ’) 0 7 3 8 4 1 Recorrer una lista de forma ascendente en los ı́ndices pares y luego descendente en los impares: def asc_desc(a): r = [] for n, x in enumerate(a): if n % 2 == 1: r.append(x) else: yield x for x in reversed(r): yield x Luego recorremos con un for normal: >>> a = [1, 4, 8, 3, 7, 0] >>> for x in asc_desc(a): ... print(x, end=’ ’) 1 8 7 0 3 4 Maximiliano Geier (UBA) Python avanzado 24/06/2015 12 / 32 itertools Hay muchas formas de iterar ya definidas en el módulo itertools2 : >>> [’’.join(x) for x in itertools.permutations(’HOLA’, 2)] [’HO’, ’HL’, ’HA’, ’OH’, ’OL’, ’OA’, ’LH’, ’LO’, ’LA’, ’AH’, ’AO’, ’AL’] Los >>> >>> ... [1, iteradores se pueden componer: a = [1, 4, 8, 3, 7, 0] list(itertools.chain(itertools.islice(a, 0, None, 2), \ reversed(a[1::2]))) 8, 7, 0, 3, 4] Ojo con la legibilidad (bis): for n, elem in enumerate(lst): for comb in itertools.imap(lambda x: (elem,) + x, \ itertools.combinations(itertools.compress(lst, \ repeat_except(1, size, 0, n)), r_tail)): yield comb 2 http://docs.python.org/3/library/itertools.html Maximiliano Geier (UBA) Python avanzado 24/06/2015 13 / 32 Formas de iterar diccionarios También podemos usar el for variable in para iterar diccionarios: >>> d = {1: ’Esteban’, 2:’Maria Elena’, 3:’Maximiliano’, 4:’Zombie’} >>> for k in d: ... print(k, end=’ ’) 1 2 3 4 >>> for v in d.values(): ... print(v, end=’ ’) Esteban Maria Elena Maximiliano Zombie >>> for t in d.items(): ... print(t, end=’ ’) (1, ’Esteban’) (2, ’Maria Elena’) (3, ’Maximiliano’) (4, ’Zombie’) Cada contenedor en Python (listas, tuplas, diccionarios, etc.) tiene definida su forma de recorrerse. El uso del for nos abstrae esta noción sin que tengamos que preocuparnos por qué estamos recorriendo. Maximiliano Geier (UBA) Python avanzado 24/06/2015 14 / 32 Otros métodos especiales Además de __init__, __enter__, __leave__ y __iter__, otros métodos con nombres especiales permiten definir cómo funcionan ciertas operaciones entre objetos de esa clase. __eq__(self, b): igualdad entre self y b (devuelve un booleano). __del__(self): destructor de la clase (no devuelve nada). __str__(self): conversión de self a string (devuelve una cadena de caracteres). Ejemplo: __str__(self) para ArbolBinario. def __str__(self): return ’, ’.join(self.inorder()) Uso: >>> print(ab) 1, 3, 2 __repr__(self): representación textual para self. def __repr__(self): return str(self) Uso: >>> print([ab]) [’1, 3, 2’] Maximiliano Geier (UBA) Python avanzado 24/06/2015 15 / 32 Equivalencias entre formas de iterar a lo Python y el while for x in obj: try: i = iter(obj) while True: x = next(i) # hago algo except StopIteration: pass Para que esto funcione, tiene que estar definido __iter__(self) para el tipo de obj. if x in obj: try: found = False i = iter(obj) while not next(i) == x: pass except StopIteration: pass else: found = True if found: # hago algo Este if requiere además que esté definido __eq__(self, b) para el tipo de x. Maximiliano Geier (UBA) Python avanzado 24/06/2015 16 / 32 Tuple unpacking Pasar tuplas como listas de argumentos a funciones: def f(x1, x2, x3): return x1 + x2 + x3 x = (4, 5, 6) print(f(*x)) También permite asignar los elementos de una tupla a variables separadas en una lı́nea: x1, x2, x3 = x Swap: >>> a = 1; b = 2 >>> b, a = a, b >>> print(a, b) 2 1 Trasponer una matriz: >>> a = [[0,4], [1,-1], [2,5], [3,0]] >>> print(list(zip(*a))) [(0, 1, 2, 3), (4, -1, 5, 0)] Maximiliano Geier (UBA) Python avanzado 24/06/2015 17 / 32 Keyword arguments Permite identificar parámetros de funciones por nombre en lugar de por su posición. De esta manera algún argumento (o muchos o todos) podrı́an ser opcionales. Se pueden mezclar con argumentos posicionales. Los keyword arguments se acceden como un diccionario, donde la clave es el nombre del parámetro. def fn(*args, **kwargs): if ’arg1’ in kwargs: print(’me pasaron el argumento arg1’) fn2(kwargs[’arg1’], args[0]) else: fn3(**kwargs) Maximiliano Geier (UBA) Python avanzado 24/06/2015 18 / 32 Funciones como parámetros de otras funciones Las funciones en Python también son objetos (de la clase function), y como tales tienen sus propios atributos y se los puede pasar como parámetro a otras funciones. Factorización de código: def tomarTiempos(fn, *args, **kwargs): inicio = time.time() res = fn(*args, **kwargs) fin = time.time() print(’función: {0} tiempo: {1}’.format(fn.__name__, fin-inicio)) return res # llama a miTP(param1, param2, param3) tomarTiempos(miTP, param1, param2, param3) Funciones anónimas: >>> a = [(2,’segundo’),(3,’tercero’),(4,’cuarto’),(1,’primero’)] >>> sorted(a, key=lambda x: x[0]) [(1, ’primero’), (2, ’segundo’), (3, ’tercero’), (4, ’cuarto’)] >>> sorted(a, key=lambda x: x[1]) [(4, ’cuarto’), (1, ’primero’), (2, ’segundo’), (3, ’tercero’)] Maximiliano Geier (UBA) Python avanzado 24/06/2015 19 / 32 Funciones como parámetros de otras funciones (continuación) Decorators: def tomarTiempos(fn): def f(*args, **kwargs): inicio = time.time() res = fn(*args, **kwargs) fin = time.time() print(’función: {0} tiempo: {1}’.format(fn.__name__, fin-inicio)) return res return f @tomarTiempos def miTP(param1, param2): # hacer el TP return 0 Notar el def anidado. Llamar a miTP definido con el decorator es equivalente al siguiente código: f = tomarTiempos(miTP) f(param1, param2) # f va a llamar a miTP Maximiliano Geier (UBA) Python avanzado 24/06/2015 20 / 32 Esquemas de recursión Muchos programas recursivos tienen estructuras parecidas. Utilizando alto orden (funciones como parámetro), se pueden generalizar ciertas funciones recursivas para que sean más fáciles de escribir y de entender. Los tres esquemas de recursión que trae Python son map, filter y reduce.3 map(fn, lista): devuelve una lista como resultado de aplicar la función fn a cada elemento de lista. def map(fn, a): if len(a) == 0: return [] else: return [fn(a[0])] + map(fn, a[1:]) 3 http://www.python-course.eu/lambda.php Maximiliano Geier (UBA) Python avanzado 24/06/2015 21 / 32 Esquemas de recursión (continuación) filter(fn, a): devuelve una lista con todos los elementos de lista donde la función fn devuelva True. def filter(fn, a): if len(a) == 0: return [] else: if fn(a[0]): return [a[0]] + filter(fn, a[1:]) else: return filter(fn, a[1:]) reduce(fn, a): aplica una función fn de dos parámetros a cada elemento de a y el resultado de reducir el resto de la lista. def reduce(fn, a): if len(a) == 1: return a[0] else: return fn(a[0], reduce(fn, a[1:])) Maximiliano Geier (UBA) Python avanzado 24/06/2015 22 / 32 Esquemas de recursión (ejemplos) Lista con los cuadrados de cada elemento: def cuadrados(a): return map(lambda x: x**2, a) Lista de valores absolutos: def listaDeAbs(a): return map(abs, a) Buscar y eliminar: def buscarYEliminar(a, x): return filter(lambda y: y != x, a) Suma de todos los elementos de a: def suma(a): return reduce(lambda x, y: x + y, a) Cantidad de apariciones: def cantidadApariciones(a, x): return suma(map(lambda w: 1 if w == x else 0, a)) Todos los elementos de a son True: def todosTrue(a): return reduce(lambda x, y: x and y, a) Maximiliano Geier (UBA) Python avanzado 24/06/2015 23 / 32 Esquemas de recursión (más ejemplos) Todos pares: def todosPares(a): return todosTrue(map(lambda x: x % 2 == 0, a)) Máximo de una lista: def maximo(a): return reduce(lambda x, y: x if x > y else y, a) Suma de los valores absolutos de cada elemento de a: def sumaAbsolutos(a): return suma(listaDeAbs(a)) Nota de implementación: en Python 3 map y filter no devuelven listas sino generadores. Los generadores son estructuras iterables (con for), pero su ventaja es que no se construyen los resultados hasta que se intenta acceder a ellos, mientras que su desventaja es que no vamos a obtener todos los elementos hasta que no hagamos un for. Para lograr este efecto hay que usar listas por comprensión. Finalmente, para usar reduce hay que importarla del módulo functools. Maximiliano Geier (UBA) Python avanzado 24/06/2015 24 / 32 MapReduce El concepto de map se aplica a la programación paralela para resolver problemas del tipo embarrassingly parallel. Map, combinado con los pasos de shuffle y reduce, inspiraron un modelo de cómputo para procesar distribuidamente grandes volúmenes de datos que se llama MapReduce4 . Este modelo, sumado a un filesystem distribuido para obtener los datos, permite utilizar clusters para procesar enormes cantidades de datos en poco tiempo. Hadoop es una plataforma libre de MapReduce muy utilizada. Hay aplicaciones de MapReduce para Hadoop en múltiples campos del conocimiento5 , siempre que se necesite procesar muchos datos. Si bien Hadoop es una plataforma escrita en Java, los mappers y reducers de una aplicación los podemos escribir en Python6 . 4 5 6 https://static.googleusercontent.com/media/research.google.com/es/us/archive/mapreduce-osdi04.pdf http://bib.oxfordjournals.org/content/early/2013/02/07/bib.bbs088.full http://www.michael-noll.com/tutorials/writing-an-hadoop-mapreduce-program-in-python/ Maximiliano Geier (UBA) Python avanzado 24/06/2015 25 / 32 Equivalencia entre listas por comprensión y esquemas recursivos map: [fn(x) for x in a] map(fn, a) filter: [x for x in a if fn(x)] filter(fn, a) reduce no tiene una equivalencia en listas por comprensión: # versión Pythónica acc = a[0] for x in a[1:]: acc = fn(acc, x) # reduce acc = reduce(fn, a) La forma preferida del creador de Python es el uso de listas por comprensión, o for explı́citos en caso de que no se pueda. Maximiliano Geier (UBA) Python avanzado 24/06/2015 26 / 32 Duck Typing When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. James Whitcomb Riley (1849–1916). El sistema de tipos de Python permite que una función pensada para operar con un tipo de datos en particular, pueda funcionar con otro diferente siempre y cuando las operaciones que dicha función tiene que hacer sobre los operandos estén soportadas por ambos tipos de datos. Estas reglas sobre los tipos se las conoce como Duck Typing. En lenguajes que no hacen uso de Duck Typing, es necesario establecer otro tipo de mecanismos para que objetos de diferentes clases puedan funcionar como parámetros de una misma función. class Pato(object): def quack(): >>> p1 = Persona() return ’Cuac’ >>> p2 = Pato() class Persona(object): >>> duckify(p1) def quack(): ’Imito a un pato’ return ’Imito a un pato’ >>> duckify(p2) def duckify(d): ’Cuac’ return d.quack() Maximiliano Geier (UBA) Python avanzado 24/06/2015 27 / 32 Introspection A diferencia de ciertos lenguajes, Python nos permite pedir información del sistema de tipos a los objetos del lenguaje: isinstance(obj, cls): devuelve True si obj es una instancia de la clase cls (o de alguna de sus subclases). >>> l = Laberinto() >>> isinstance(l, object) True issubclass(subcls, cls): devuelve True si subcls es una subclase de cls. >>> issubclass(TypeError, Exception) True getattr(obj, s): devuelve el atributo o el método del objeto obj con nombre s: >>> m = getattr(l, ’resuelto’) >>> m <bound method Laberinto.resuelto of <Laberinto object at 0x7f23a7f217f0>> >>> m() False setattr(obj, k, v): define un atributo de nombre k en obj, con el valor v: >>> setattr(obj, ’resuelto’, lambda: True) >>> obj.resuelto() True Maximiliano Geier (UBA) Python avanzado 24/06/2015 28 / 32 Evaluación de código definido en runtime eval ejecuta código definido en una cadena de caracteres (no se pueden asignar variables nuevas): >>> a = "__import__(’time’).time()" >>> eval(a) 1400870545.0649445 >>> __import__(’time’).time() 1400870677.7443652 >>> eval(’2**4’) 16 >>> eval(’r = 2**4’) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 1 r = 2**4 ^ SyntaxError: invalid syntax El código se evalúa como una cadena de texto, ası́ que podrı́a venir de cualquier lado: la consola, un archivo, Internet. Ojo si no se tiene control de qué ejecutamos: >>> eval(’__import__("os").system("rm -rf /")’) Maximiliano Geier (UBA) Python avanzado 24/06/2015 29 / 32 Ejecutando programas externos Podemos utilizar Python para sistematizar la ejecución de experimentos computacionales, es decir, una serie de comandos tediosos y repetitivos que tenemos que ejecutar secuencialmente para obtener nuestro resultado. Estas funciones están incluidas en el módulo subprocess. Para ejecutar programas y capturar su salida: >>> subprocess.check_output(’echo Hello, world!’, shell=True) b’Hello, world!\n’ También podemos “usar” programas interactivos: >>> with subprocess.Popen(shlex.split(’mkpasswd -s’), \ ... stdin=subprocess.PIPE, stdout=subprocess.PIPE) as p: ... password, err = p.communicate(b’hola\n’) ... >>> password b’m11/.T/.UGjJY\n’ Maximiliano Geier (UBA) Python avanzado 24/06/2015 30 / 32 Pidiendo ayuda dir(obj): devuelve una lista con todos los métodos o atributos que tiene definido el objeto obj. >>> l = Laberinto() >>> dir(l) [’__class__’, ’__delattr__’, ’__dict__’, ’__dir__’, ’__doc__’, ’__eq__’, ’__format__’, ’__ge__’, ’__getattribute__’, ’__gt__’, ’__hash__’, ’__init__’, ’__le__’, ’__lt__’, ’__module__’, ’__ne__’, ’__new__’, ’__reduce__’, ’__reduce_ex__’, ’__repr__’, ’__setattr__’, ’__sizeof__’, ’__str__’, ’__subclasshook__’, ’__weakref__’, ’cargar’, ’esPosicionQueso’, ’esPosicionRata’, ’get’, ’getInfoCelda’, ’getPosicionQueso’, ’getPosicionRata’, ’parent’, ’resetear’, ’resolver’, ’resuelto’] También se puede usar la tecla Tab para autocompletar nombres de métodos o clases desde el intérprete de Python (no funciona en Python 2). help(obj): muestra en un formato amigable el texto escrito en obj.__doc__. Ejecutando desde la terminal pydoc3 -b, se abre un browser con la documentación de los help de cada módulo que haya instalado en el sistema (incluyendo los de la carpeta desde la que se ejecutó pydoc), vistos como una página web. Maximiliano Geier (UBA) Python avanzado 24/06/2015 31 / 32 Terminando >>> import __hello__ Hello world! >>> import antigravity >>> import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren’t special enough to break the rules. Although practicality beats purity. If the implementation is hard to explain, it’s a bad idea. If the implementation is easy to explain, it may be a good idea. Maximiliano Geier (UBA) Python avanzado 24/06/2015 32 / 32
© Copyright 2024