#!/usr/bin/env python # coding: utf-8 # # Taller de Python - Estadística en Física Experimental - 1er día # # ![taller_python](logos_python_fifa.png) # # Esta presentación/notebook está disponible: # [Repositorio Github FIFA BsAs](https://github.com/fifabsas/talleresfifabsas/blob/master/python/Incertezas/introduccion.ipynb) (para descargarlo, usen el botón `raw` o hagan un `fork` del repositorio) # [Página web de talleres FIFA BsAs](http://fifabsas.github.io/talleresfifabsas) # ## Programar ¿con qué se come? # # Programar es dar una lista de tareas concretas a la computadora para que haga. Esencialmente, una computadora sabe: # # - Leer datos # - Escribir datos # - Transformar datos # # Y nada más que esto,. Así, la computadora pasa a ser suna gran gran calculadora que permite hacer cualquier tipo de cuenta de las que necesitemos dentro de la Física (y de la vida también) mientras sepamos cómo decirle a la máquina qué cómputos hacer. # # ### Pero, ¿qué es Python? # Python es un lenguaje para hablarle a la computadora, que se denominan _lenguajes de programación_. Este lenguaje, que puede ser escrito y entendido por la computadora debe ser transformado a un lenguaje que entieda la computadora (o un intermediario, que se denomina maquina virtual) así se hacen las transformaciones. Todo este modelo de programación lo podemos ver esquematizado en la figura siguiente # # Drawing # # ### Historia # Python nació en 1991, cuando su creador Guido Van Rossum lo hizo público en su versión 0.9. El lenguaje siempre buscó ser fácil de aprender y poder hacer tareas de todo tipo. Es fácil de aprender por su sintaxis, el tipado dinámico (que vamos a ver de que se trata) y además la gran cantidad de librerías/módulos para todo. # # # Herramientas para el taller # Para trabajar vamos a usar algún editor de texto (recomendamos **Visual Studio Code**, que viene con Anaconda), una terminal, o directamente el editor **Spyder** (que pueden buscarlo en las aplicaciones de la computadora si instalaron Anaconda o si lo instalaron en la PC del aula). También, si quieren podemos trabajar en un **Jupyter Notebook**, que permite hacer archivos como este (y hacer informes con código intercalado) # # Esto es a gusto del consumidor, sabemos usar todas esas herramientas. Cada una tiene sus ventajas y desventajas: # - Escribir y ejecutar en consola no necesita instalar nada más que Python. Aprender a usar la consola da muchos beneficios de productividad # - El editor o entorno de desarrollo al tener más funcionalidad es más pesado, y probablemente sea más caro (Pycharm, que es el entorno de desarrollo más completo de Python sale alrededor de 200 dolares... auch) # - Jupyter notebook es un entorno muy interactivo, pero puede traer problemas en la lógica de ejecución. Hay que tener cuidado # # Para instalar Python, conviene descargarse [Anaconda](https://www.anaconda.com/download). Este proyecto corresponde a una distribución de Python, que al tener una interfaz grafica amigable y manejador de paquetes llamado _conda_ te permite instalar todas las librerías científicas de una. En Linux y macOS instalar Python sin Anaconda es más fácil, en Windows diría que es una necesidad sin meterse en asuntos oscuros de compilación (y además que el soporte en Windows de las librerías no es tan amplio). # # Existe un proyecto llamado [pyenv](https://github.com/pyenv/pyenv) que en Linux y macOS permite instalar cualquier versión de Python. Si lo quieren tener (aunque para empezar Anaconda es mejor) pregunte que lo configuramos rápidamente. # ## Datos, memoria y otras yerbas # Para hacer cuentas, primero necesitamos el medio para guardar o almacenar los datos. El sector este se denomina _memoria_. Nuestros datos se guardan en _espacios de memoria_, y esos espacios tienen un nombre, un rótulo con el cual los podremos llamar y pedirle a la computadora que los utilice para operar con ellos, los modifique, etc. # # Como esos espacios son capaces de variar al avanzar los datos llegamos a llamarlos _variables_, y el proceso de llenar la variable con un valor se denomina _asignación_, que en Python se corresponde con el "=". # # Hasta ahora sólo tenemos en la cabeza valores numéricos para nuestras variables, considerando la analogía de la super-calculadora. Pero esto no es así, y es más las variables en Python contienen la información adicional del _tipo de dato_. Este tipo de dato determina las operaciones posibles con la variable (además del tamaño en memoria, pero esto ya era esperable del mismo valor de la variable). # # Veamos un par de ejemplos # In[1]: x = 5 y = 'Hola mundo!' z = [1,2,3] # Aquí hemos guardado en un espacio de memoria llamado por nosotros "x" la información de un valor de tipo entero, 5, en otro espacio de memoria, que nosotros llamamos "y" guardamos el texto "Hola mundo!". En Python, las comillas indican que lo que encerramos con ellas es un texto. x no es un texto, así que Python lo tratará como variable para manipular. "z" es el nombre del espacio de memoria donde se almacena una lista con 3 elementos enteros. # # Podemos hacer cosas con esta información. Python es un lenguaje interpretado (a diferencia de otros como Java o C++), eso significa que ni bien nosotros le pedimos algo a Python, éste lo ejecuta. Así es que podremos pedirle por ejemplo que imprima en pantalla el contenido en y, el tipo de valor que es x (entero) entre otras cosas. # In[2]: print(y) print(type(x)) print(type(y), type(z), len(z)) # Vamos a utilizar mucho la función *type()* para entender con qué tipo de variables estamos trabajando. *type()* es una función predeterminada por Python, y lo que hace es pedir como argumento (lo que va entre los paréntesis) una variable y devuelve inmediatamente el tipo de variable que es. # ### Ejercicio 1 # # En el siguiente bloque cree las variables "dato1" y "dato2" y guarde en ellas los textos "estoy programando" y "que emocion!". Con la función *type()* averigue qué tipo de datos se almacena en esas variables. # In[3]: # Realice el ejercicio 1 # # # Para las variables *integers*(enteros) y *floats* (flotantes) podemos hacer las operaciones matemáticas usuales y esperables. Veamos un poco las compatibilidades entre estos tipos de variables. # In[4]: a = 5 b = 7 c = 5.0 d = 7.0 print(a+b, b+c, a*d, a/b, a/d, c**2) # ### Ejercicio 2 # # Calcule el resultado de $$ \frac{(2+7.9)^2}{4^{7.4-3.14*9.81}-1} $$ y guárdelo en una variable # In[5]: # Realice el ejercicio 2. El resultado esperado es -98.01 # ## Listas, tuplas y diccionarios # Las **listas** son cadenas de datos de cualquier tipo, unidos por estar en una misma variable, con posiciones dentro de esa lista, con las cuales nosotros podemos llamarlas. En Python, las listas se enumeran desde el 0 en adelante. # # Estas listas también tienen algunas operaciones que le son válidas. # # Distintas son las **tuplas**. Las listas son editables (en jerga, _mutables_), pero las tuplas no (_inmutables_). Esto es importante cuando, a lo largo del desarrollo de un código donde necesitamos que ciertas cosas no cambien, no editemos por error valores fundamentales de nuestro problema a resolver. # In[6]: lista1 = [1, 2, 'saraza'] print(lista1, type(lista1)) print(lista1[1], type(lista1[1])) print(lista1[2], type(lista1[2])) print(lista1[-1]) # In[7]: lista2 = [2,3,4] lista3 = [5,6,7] #print(lista2+lista3) print(lista2[2]+lista3[0]) # In[8]: tupla1 = (1,2,3) lista4 = [1,2,3] lista4[2] = 0 print(lista4) #tupla1[0] = 0 print(tupla1) # Hay formas muy cómodas de hacer listas. Presentamos una que utilizaremos mucho, que es usando la función *range*. Esta devuelve como una _receta_ de como hacer los numeros; por lo tanto tenemos que decirle al generador que cree la lista, por medio de otra herramienta incorporada de Python, *list* # In[9]: listilla = list(range(10)) print(listilla, type(listilla)) # Cómo en general no se hace seguido esto, no existe una forma "rápida" o "más elegante" de hacerlo. # ### Ejercicio 3 # # 1. Haga una lista con los resultados de los últimos dos ejercicios y que la imprima en pantalla # # * Sobreescriba en la misma variable la misma lista pero con sus elementos permutados e imprima nuevamente la lista # # Ejemplo de lo que debería mostrarse en pantalla # # `['estoy programando', 'que emocion!', -98.01]` # # `['estoy programando', -98.01, 'que emocion!']` # In[10]: # Realice el ejercicio 3 # ### Ejercicio 4 # # 1. Haga una lista con la función *range* de 15 elementos y sume los elementos 5, 10 y 12 # # * Con la misma lista, haga el producto de los primeros 4 elementos de esa lista # # * Con la misma lista, reste el último valor con el primero # In[11]: # Realice el ejercicio 4 # Ahora, el titulo hablaba de diccionarios... pero no son los que usamos para buscar el significado de las palabras. ¡Aunque pueden ser parecidos o funcionar igual!. # # Un diccionario es un relación entre una variable llamada llave y otra variable llamado valor. Relación en el sentido de función que veíamos en el secundario, pero usualmente de forma discreta. # # La magia es que sabiendo la llave, o _key_, ya tienes el valor, o _value_, por lo que podés usarlo como una lista pero sin usar indices si no cosas como cadenas. Las keys son únicas, y si quiero crear un diccionario con las mismas keys se van a pisar y queda la última aparición # # Veamos un ejemplo # In[12]: d = {"hola": 1, "mundo": 2, 0: "numero", (0, 1): ["tupla", 0, 1]} # Las llaves pueden ser casi cualquier cosa (lista no) print(d, type(d)) print(d["hola"]) print(d[0]) print(d[(0, 1)]) # Podés setear una llave (o key) vieja d[0] = 10 # O podes agregar una nueva. El orden de las llaves no es algo en qué confiar necesariamente, para eso está OrderedDict d[42] = "La respuesta" # Cambiamos el diccionario, así que aparecen nuevas keys y cambios de values print(d) # Keys repetidas terminan siendo sobreescritas rep_d = {0: 1, 0: 2} print(rep_d) # Otra cosas menor, un diccionario vacío es empt_d = {} print(empt_d) # Es particularmente mágico el diccionario y lo podes usar para muchisimas cosas (y además Python lo usa para casi todo internamente, así que está muy bueno saber usarlos!). # # El largo de un diccionario es la cantidad de keys que tiene, por ejemplo # In[13]: new_d = {0: '0', '0': 0} print(len(new_d)) # Diccionario vacío print(len({})) # ### Ejercicio 5 # Haga un diccionario con tal que con el siguiente código # # ``` # print(tu_dict[1] + tu_dict["FIFA"] + tu_dict[(3,4)]) # ``` # # Imprima "Programador, hola mundo!". Puede tener todas las entradas que quieras, no hay limite de la creatividad acá # In[14]: # Realice el ejercicio 5 # Descomente esta línea y a trabajar # print(tu_dict[1] + tu_dict["FIFA"] + tu_dict[(3,4)]) # ## Booleans # Este tipo de variable tiene sólo dos valores posibles: 1 y 0, o *True* y *False*. Las utilizaremos escencialmente para que Python reconozca relaciones entre números. # In[15]: print(5 > 4) print(4 > 5) print(4 == 5) #La igualdad matemática se escribe con doble == print(4 != 5) #La desigualdad matemática se escribe con != print(type(4 > 5)) # También podemos comparar listas, donde todas las entradas deberíán ser iguales # In[16]: print([1, 2, 3] == [1, 2, 3]) print([1, 2, 3] == [1, 3, 2]) # Lo mismo para tuplas (y aplica para diccionarios) # In[17]: print((0, 1) == (0, 1)) print((1, 3) == (0, 3)) # Con la función _id()_ podemos ver si dos variables apuntan a la misma dirección de memoria, es decir podemos ver si dos variables tienen exactamente el mismo valor (aunque sea filosófico, en Python la diferencia es importante) # In[18]: a = 5 b = a print(id(a) == id(b)) a = 12 # Reutilizamos la variable, con un nuevo valor b = 12 print(id(a) == id(b)) # Python cachea números de 16bits a = 66000 b = 66000 print(id(a) == id(b)) # No cachea listas, ni strings a = [1, 2, 3] b = [1, 2, 3] print(id(a) == id(b)) a = "Python es lo más" b = "Python es lo más" print(id(a) == id(b)) # Las listas, tuplas y diccionarios también pueden devolver booleanos cuando se le pregunta si tiene o no algún elemento. Los diccionarios trabajaran sobre las llaves y las listas/tuplas sobre sus indices/valores # In[19]: nueva_l = [0, 42, 3] nueva_t = (2.3, 4.2); nuevo_d = {"0": -4, (0, 1): "tupla"} # La frase es # >>> x in collection # donde collection es una tupla, lista o diccionario. Parece inglés escrito no? print(42 in nueva_l) print(3 in nueva_t) print((0,1) in nuevo_d) # ### Ejercicio 6 # # Averigue el resultado de `4!=5==1`. ¿Dónde pondría paréntesis para que el resultado fuera distinto? # In[20]: # Realice el ejercicio 5 # ## Control de flujo: condicionales e iteraciones (if y for para los amigos) # Si en el fondo un programa es una serie de algoritmos que la computadora debe seguir, un conocimiento fundamental para programar es saber cómo pedirle a una computadora que haga operaciones si se cumple una condición y que haga otras si no se cumple. Nos va a permitir hacer programas mucho más complejos. Veamos entonces como aplicar un *if*. # In[21]: parametro = 5 if parametro > 0: # un if inaugura un nuevo bloque indentado print('Tu parametro es {} y es mayor a cero'.format(parametro)) print('Gracias') else: # el else inaugura otro bloque indentado print('Tu parametro es {} y es menor o igual a cero'.format(parametro)) print('Gracias') print('Vuelva pronto') print(' ') # In[22]: parametro = -5 if parametro > 0: # un if inaugura un nuevo bloque indentado print('Tu parametro es {} y es mayor a cero'.format(parametro)) print('Gracias') else: # el else inaugura otro bloque indentado print('Tu parametro es {} y es menor o igual a cero'.format(parametro)) print('Gracias') print('Vuelva pronto') print(' ') # ### Ejercicio 7 # # Haga un programa con un *if* que imprima la suma de dos números si un tercero es positivo, y que imprima la resta si el tercero es negativo. # In[23]: # Realice el ejercicio 7 # Para que Python repita una misma acción *n* cantidad de veces, utilizaremos la estructura *for*. En cada paso, nosotros podemos aprovechar el "número de iteración" como una variable. Eso nos servirá en la mayoría de los casos. # In[24]: nueva_lista = ['nada',1,2,'tres', 'cuatro', 7-2, 2*3, 7/1, 2**3, 3**2] for i in range(10): # i es una variable que inventamos en el for, y que tomará los valores de la print(nueva_lista[i]) #lista que se genere con range(10) # ### Ejercicio 8 # # 1. Haga otra lista con 16 elementos, y haga un programa que con un *for* imprima solo los primeros 7 # * Modifique el *for* anterior y haga que imprima solo los elementos pares de su lista # In[25]: # Realice el ejercicio 8 # La estructura *while* es poco recomendada en Python pero es importante saber que existe: consiste en repetir un paso mientras se cumpla una condición. Es como un *for* mezclado con un *if*. # In[26]: i = 1 while i < 10: # tener cuidado con los while que se cumplen siempre. Eso daría lugar a los loops infinitos. i = i+1 print(i) # ### Ejercicio 9 # # 1. Calcule el factorial de N, siendo N la única variable que recibe la función (Se puede pensar usando *for* o usando *while*). # * Calcule la sumatoria de los elementos de una lista. # In[27]: # Realice el ejercicio 8 # ## Funciones # Pero si queremos definir nuestra propia manera de calcular algo, o si queremos agrupar una serie de órdenes bajo un mismo nombre, podemos definirnos nuestras propias funciones, pidiendo la cantidad de argumentos que querramos. # # Vamos a usar las funciones *lambda* (también llamadas anonimas) más que nada para funciones matemáticas, aunque también tenga otros usos. Definamos el polinomio $f(x) = x^2 - 5x + 6$ que tiene como raíces $x = 3$ y $x = 2$. # In[28]: f = lambda x: x**2 - 5*x + 6 print(f(3), f(2), f(0)) # Las funciones *lambda* son necesariamente funciones de una sola linea y también tienen que retornar nada; por eso son candidatas para expresiones matemáticas simples. # # Las otras funciones, las más generales, se las llama funciones *def*, y tienen la siguiente forma. # In[29]: def promedio(a,b,c): N = a + b + c # Es importante que toda la función tenga su contenido indentado N = N/3.0 return N mipromedio = promedio(5,5,7) # Aquí rompimos la indentación print(mipromedio) # Algo muy interesante y curioso, es que podemos hacer lo siguiente con las funciones # In[30]: def otra_funcion(a, b): return a + b * 2 # Es un valor! otra_f = otra_funcion print(otra_f) print(type(otra_f)) print(otra_f(2, 3)) # Las funciones pueden ser variables y esto abre la puerta a muchas cosas. Si tienen curiosidad, pregunten que está re bueno esto! # ### Ejercicio 10 # # Hacer una función que calcule el promedio de $n$ elementos dados en una lista. # # **Sugerencia**: utilizar las funciones *len()* y *sum()* como auxiliares. # In[31]: # Realice el ejercicio 9 # ### Ejercicio 11 # # Usando lo que ya sabemos de funciones matemáticas y las bifurcaciones que puede generar un *if*, hacer una función que reciba los coeficientes $a, b, c$ de la parábola $f(x) = ax^2 + bx + c$ y calcule las raíces *si* son reales (es decir, usando el discriminante $\Delta = b^2 - 4ac$ como criterio), y sino que imprima en pantalla una advertencia de que el cálculo no se puede hacer en $\mathbb{R}$. # In[32]: # Realice el ejercicio 10 # ### Bonus track 1 # # Modificar la función anterior para que calcule las raíces de todos modos, aunque sean complejas. Python permite usar números complejos escritos de la forma `1 + 4j`. Investiguen un poco # In[33]: # Bonus track 1 # ### Ejercicio 12 # # Repitan el ejercicio 8, es decir # 1. Hacer una función que calcule el factorial de N, siendo N la única variable que recibe la función (Se puede pensar usando *for* o usando *while*). # * Hacer una función que calcule la sumatoria de los elementos de una lista. # # ¿Se les ocurre otra forma de hacer el factorial? Piensen la definición matemática y escribanla en Python, y prueben calcular el factorial de 100 con esta definición nueva # In[34]: # Realice el ejercicio 12 # ## Paquetes y módulos # Pero las operaciones básicas de suma, resta, multiplicación y división son todo lo que un lenguaje como Python puede hacer "nativamente". Una potencia o un seno es álgebra no lineal, y para hacerlo, habría que inventarse un algoritmo (una serie de pasos) para calcular por ejemplo *sen($\pi$)*. Pero alguien ya lo hizo, ya lo pensó, ya lo escribió en lenguaje Python y ahora todos podemos usar ese algoritmo sin pensar en él. Solamente hay que decirle a nuestro intérprete de Python dónde está guardado ese algoritmo. **Esta posibilidad de usar algoritmos de otros es fundamental en la programación, porque es lo que permite que nuestro problema se limite solamente a entender cómo llamar a estos algoritmos ya pensados y no tener que pensarlos cada vez**. # # Vamos entonces a llamar a un *paquete* (como se le llama en Python) llamada *math* que nos va a extender nuestras posibilididades matemáticas. # In[35]: import math # Llamamos a una biblioteca r1 = math.pow(2,4) r2 = math.cos(math.pi) r3 = math.log(100,10) r4 = math.log(math.e) print(r1, r2, r3, r4) # Para entender cómo funcionan estas funciones, es importante recurrir a su *documentation*. La de esta biblioteca en particular se encuentra en # # https://docs.python.org/2/library/math.html # ### Ejercicio 13 # # Use Python como calculadora y halle los resultados de # # 1. $\log(\cos(2\pi))$ # * $\text{atanh}(2^{\cos(e)} -1) $ # * $\sqrt{x^2+2x+1}$ con $x = 125$ # # # In[36]: # Realice el ejercicio 13 # ### Crear bibliotecas # # Bueno, ahora que sabemos como usar bibliotecas, nos queda saber cómo podemos crearlas. Pero para saber eso, tenemos que saber que es un _módulo_ en Python y cómo se relaciona con un paquete. # # Se le llama módulo a los archivos de Python, archivos con la extensión \*.py, como por ejemplo *taller_python.py* (como tal vez algunos hicieron ya). En este archivo se agregan funciones, variables, etc, que pueden ser llamadas desde otro módulo con el nombre sin la extensión, es decir # In[37]: import taller_python # Vean el repositorio! # Python para buscar estos módulos revisa si el módulo importado (con el comando `import`) está presente en la misma carpeta del que importa y luego en una serie de lugares estándares de Python (que se pueden alterar y revisar usando `sys.path`, importando el paquete `sys`). Si lo encuentra lo importa y podés usar las funciones, y si no puede salta una excepción # In[38]: print(taller_python.func(5, 6)) # Veamos la documentación help(taller_python.func) # Traten de importar la función `__func_oculta`. Se puede, pero es un hack de Python y la idea es que no sepa de ella. Es una forma de ocultar y encapsular código, que es uno de los principios de la _programación orientada a objetos_. # # Finalmente, un paquete como `math` es un conjunto de módulos ordenados en una carpeta con el nombre `math`, con un archivo especial `__init__.py`, que hace que la carpeta se comporte como un módulo. Python importa lo que vea en el archivo `__init__.py` y permite además importar los módulos dentro (o submodulos), si no tienen guiones bajos antes. # # Usualmente no es recomendable trabajar en el `__init__.py`, salvo que se tenga una razón muy necesaria (o simplemente vagancia) # ### Ejercicio 14 # Creen una libraría llamada `mi_taller_python` y agregen dos funciones, una que devuelva el resultado de $\sqrt{x^2+2x+1}$ para cualquier x y otra que resuelva el resultado de $(x^2+2x+1)^{y}$, para cualquier x e y. Hagan todas las funciones ocultas que requieran (aunque recomendamos siempre minimzarlas) # In[39]: # Realice el ejercicio 14 # ### Bonus track 2 # # Ahora que nos animamos a buscar nuevas bibliotecas y definir funciones, buscar la función *newton()* de la biblioteca **scipy.optimize** para hallar $x$ tal que se cumpla la siguiente ecuación no lineal $$\frac{1}{x} = ln(x)$$ # In[40]: #Acá va el bonus track 2, para ya saborear la próxima clase # Con esto terminamos la primera sesión del taller! Para la próxima vamos a aprender a manejar muchos datos al mismo tiempo, graficarlos y crear datos estadísticos, usando un par de librerías especificas del set científico de Python (*numpy*, *scipy* y *matplotlib*). # # En nuestro repositorio en Github (https://github.com/fifabsas/talleresfifabsas) está colgado este material así como el de la próxima clase. Además tiene ejemplos (hasta de automatización de instrumental) y otras instancias de talleres que hemos dado a traves del tiempo. # # La importancia de las referencias # # Para más referencias pueden googlear. Dejamos algunas de referencia: # # http://pybonacci.org/2012/06/07/algebra-lineal-en-python-con-numpy-i-operaciones-basicas/ # # http://relopezbriega.github.io/blog/2015/06/14/algebra-lineal-con-python/ # # http://pendientedemigracion.ucm.es/info/aocg/python/modulos_cientificos/numpy/index.html # # Pero es importantísimo manejarse con la documentación de las bibliotecas que se utilizan # # https://docs.python.org/2/library/math.html # # http://docs.scipy.org/doc/numpy/reference/routines.linalg.html # # http://matplotlib.org/api/pyplot_api.html # # Recursos # # Para seguir profundizando con la programación en Python, ofrecemos distintos recursos # # Un tutorial: http://www.learnpython.org/ # # *How to think like a computer scientist* (aprendizaje interactivo): http://interactivepython.org/runestone/static/thinkcspy/index.html # # Otro tutorial, en inglés, pero muy completo: http://learnpythonthehardway.org/book # # Coursera, que nunca está de más: https://www.coursera.org/learn/interactive-python-1 # # Otro más: https://es.coursera.org/learn/python # # Y por fuera del taller, seguimos en contacto. Tenemos un grupo de Facebook donde pueden hacerse consultas y otros chicos que fueron al taller antes o aprendieron por sus medios podrán responderles. El grupo es https://www.facebook.com/groups/303815376436624/?fref=ts # # # # # Agradecimientos # # Todo esto es posible gracias al aporte de mucha gente. # * A los docentes de la materia, por darnos el espacio para ayudar y que se lleve a cabo este taller. # * Gente muy copada del DF como Hernán Grecco, Guillermo Frank y Agustín Corbat por hacer aportes a estos talleres de diferentes maneras, desde poner su apellido para que nos presten un labo hasta venir como invitado a un taller. # * El Departamento de Computación que cuatrimestre a cuatrimestre nos presta los labos desinteresadamente. # * Pibes de la FIFA que prestan su tiempo a organizar el material y llevan a cabo el taller. # * Todos los que se acercan y piden que estos talleres se sigan dando y nos siguen llenando los Labos. Sí ¡Gracias a todos ustedes!