.. _clase_09: ======================================== Clase 9: Control de versiones y módulos ======================================== ============================== .. role:: strike :class: strike .. note:: Esta clase está :strike:`copiada` (muy fuertemente) inspirada en las siguientes fuentes: * `Lecciones de Software Carpentry `__, y la `versión en castellano `__). * El libro `Pro Git `__ de Scott Chacon y Ben Straub, y la `versión en castellano `__. Ambos disponibles libremente. * El libro `Mastering git `__ escrito por Jakub Narębski. ¿Qué es y para qué sirve el control de versiones? ================================================= El control de versiones permite ir grabando puntos en la historia de la evolución de un proyecto. Esta capacidad nos permite: - Acceder a versiones anteriores de nuestro trabajo ("undo ilimitado") - Trabajar en forma paralela con otras personas sobre un mismo documento. Habitualmente, nos podemos encontrar con situaciones como esta: .. figure:: figuras/alternativa.png :alt: o, más gracioso pero igualmente común, esta otra: .. figure:: figuras/phd101212s.png :alt: “Piled Higher and Deeper” por Jorge Cham, http://www.phdcomics.com Todos hemos estado en esta situación alguna vez: parece ridículo tener varias versiones casi idénticas del mismo documento. Algunos procesadores de texto nos permiten lidiar con esto un poco mejor, como por ejemplo el `Track Changes de Microsoft Word `__, el `historial de versiones de Google `__, o el `Track-changes de LibreOffice `__. Estas herramientas permiten solucionar el problema del trabajo en colaboración. Si tenemos una versión de un archivo (documento, programa, etc) podemos compartirlo con los otros autores para modificar, y luego ir aceptando o rechazando los cambios propuestos. Algunos problemas aún aparecen cuando se trabaja intensivamente en un documento, porque al aceptar o rechazar los cambios no queda registro de cuáles eran las alternativas. Además, estos sistemas actúan sólo sobre los documentos; en nuestro caso puede haber datos, gráficos, etc que cambien (o que queremos estar seguros que no cambiaron y estamos usando la versión correcta). En el fondo, la manera de evitar esto es manteniendo una buena organización. Una posible buena manera es designar una persona responsable, que vaya llevando la contabilidad de quién hizo qué correcciones, las integre en un único documento, y vaya guardando copias de todas los documentos que recibe en un lugar separado. Cuando hay varios autores (cuatro o cinco) éste es un trabajo bastante arduo y con buenas posibilidades de pequeños errores. Los sistemas de control de versiones tratan de automatizar la mayor parte del trabajo para hacer más fácil la colaboración, manteniendo registro de los cambios que se hicieron desde que se inició el documento, y produciendo poca interferencia, permitiendo al mismo tiempo trabajar de manera similar a como lo hacemos habitualmente. Consideremos un proyecto con varios archivos y autores. En este esquema de trabajo, podemos compartir una versión de todos los archivos del proyecto Cambios en paralelo ------------------- Una de las ventajas de los sistemas de control de versiones es que cada autor hace su aporte en su propia copia (en paralelo) .. figure:: figuras/versions.png :alt: versiones corregidas en paralelo y después estos son compatibilizados e incorporados en un único documento. .. figure:: figuras/merge.png :alt: Compaginando versiones corregidas en paralelo En casos en que los autores trabajen en zonas distintas la compaginación se puede hacer en forma automática. Por otro lado, si dos personas cambian la misma frase obviamente se necesita tomar una decisión y la compaginación no puede (ni quiere) hacerse automáticamente. Historia completa ----------------- Otra característica importante de los sistemas de control de versiones es que guardan la historia completa de todos los archivos que componen el proyecto. Imaginen, por ejemplo, que escribieron una función para resolver parte de un problema. La función no sólo hace su trabajo sino que está muy bien escrita, es elegante y funciona rápidamente. Unos días después encuentran que toda esa belleza era innecesaria, porque el problema que resuelve la función no aparece en su proyecto, y por supuesto la borra. La version oficial no tiene esa parte del código. Dos semanas, y varias líneas de código, después aparece un problema parecido y querríamos tener la función que borramos ... Los sistemas de control de versiones guardan toda la información de la historia de cada archivo, con un comentario del autor. Este modo de trabajar nos permite recuperar (casi) toda la información previa, incluso aquella que en algún momento decidimos descartar. Instalación ----------- Vamos a describir uno de los posibles sistemas de control de versiones, llamado *git* En Linux, usando el administrador de programas, algo así como: en Ubuntu:: $ sudo apt-get install git o usando `dnf` en Fedora:: $ sudo dnf install git En Windows, se puede descargar `Git for Windows `__ desde `este enlace `__, ejecutar el instalador y seguir las instrucciones. Viene con una terminal y una interfaz gráfica. Interfaces gráficas ------------------- Existen muchas interfaces gráficas, para todos los sistemas operativos. Ver por ejemplo `Git Extensions `__, `git-cola `__, `Code Review `__, o cualquiera de esta larga lista de `interfaces gráficas a Git `__. Documentación ------------- Buscando en internet el término `git` se encuentra mucha documentación. En particular el libro Pro Git tiene información muy accesible y completa. El programa se usa en la forma: .. code-block:: shell $ git [opciones] Por ejemplo, para obtener ayuda directamente desde el programa, se puede utilizar cualquiera de las opciones: .. code-block:: bash $ git help $ git --help que nos da información sobre su uso, y cuáles son los comandos disponibles. Si queremos obtener información sobre un comando en particular, agregamos el comando de interés. Para el comando de configuración sería: .. code-block:: bash $ git config --help $ git help config Configuración básica -------------------- Una vez que está instalado, es conveniente configurarlo desde una terminal, con los comandos: .. code-block:: bash $ git config --global user.name "Juan Fiol" $ git config --global user.email "fiol@cab.cnea.gov.ar" Si necesitamos usar un proxy para acceder fuera del lugar de trabajo: .. code-block:: bash $ git config --global http.proxy proxy-url $ git config --global https.proxy proxy-url Acá hemos usado la opción `--global` para que las variables configuradas se apliquen a todos los repositorios con los que trabajamos. Si necesitamos desabilitar una variable, por ejemplo el proxy, podemos hacer: .. code-block:: bash $ git config --global unset http.proxy $ git config --global unset https.proxy Creación de un nuevo repositorio -------------------------------- Si ya estamos trabajando en un proyecto, tenemos algunos archivos de trabajo, sin control de versiones, y queremos empezar a controlarlo, inicializamos el repositorio local con: .. code-block:: bash $ git init Este comando sólo inicializa el repositorio, no pone ningún archivo bajo control de versiones. Clonación de un repositorio existente ------------------------------------- Otra situación bastante común ocurre cuando queremos tener una copia local de un proyecto (grupo de archivos) que ya existe y está siendo controlado por git. En este caso utilizamos el comando `clone` en la forma: .. code-block:: bash $ git clone [nombre-local] donde el argumento `nombre-local` es opcional, si queremos darle a nuestra copia un nombre diferente al que tiene en el repositorio Ejemplos: .. code-block:: bash $ git clone /home/fiol/my-path/programa $ git clone /home/fiol/my-path/programa programa-de-calculo $ git clone https://github.com/fiolj/intro-python-IB.git $ git clone https://github.com/fiolj/intro-python-IB.git clase-ib Los dos primeros ejemplos realizan una copia de trabajo de un proyecto alojado también localmente. En el segundo y cuarto casos les estamos dando un nuevo nombre a la copia local de trabajo. En los últimos tres ejemplos estamos copiando proyectos alojados en repositorios remotos, cuyo uso es bastante popular: `bitbucket `__, `gitlab `__, y `github `__. Lo que estamos haciendo con estos comandos es copiar no sólo la versión actual del proyecto sino toda su historia. Después de ejecutar este comando tendremos en nuestra computadora cada versión de cada uno de los archivos del proyecto, con la información de quién hizo los cambios y cuándo se hicieron. Una vez que ya tenemos una copia local de un proyecto vamos a querer trabajar: modificar los archivos, agregar nuevos, borrar alguno, etc. Ver el estado actual -------------------- Para determinar qué archivos se cambiaron utilizamos el comando `status`: .. code-block:: bash $ cd my-directorio $ git status Creación de nuevos archivos y modificación de existentes -------------------------------------------------------- Después de trabajar en un archivo existente, o crear un nuevo archivo que queremos controlar, debemos agregarlo al registro de control: .. code-block:: bash $ git add $ git add Esto sólo agrega la versión actual del archivo al listado a controlar. Para incluir una copia en la base de datos del repositorio debemos realizar lo que se llama un "commit" .. code-block:: bash $ git commit -m "Mensaje para recordar que hice con estos archivos" La opción `-m` y su argumento (el *string* entre comillas) es un mensaje que dejamos grabado, asociado a los cambios realizados. Puede realizarse el `commit` sin esta opción, y entonces `git` abrirá un editor de texto para que escribamos el mensaje (que no puede estar vacío). Actualización de un repositorio remoto -------------------------------------- Una vez que se añaden o modifican los archivos, y se agregan al repositorio local, podemos enviar los cambios a un repositorio remoto. Para ello utilizamos el comando: .. code-block:: bash $ git push De la misma manera, si queremos obtener una actualización del repositorio remoto (poque alguien más la modificó), utilizamos el (los) comando(s): .. code-block:: bash $ git fetch Este comando sólo actualiza el repositorio, pero no modifica los archivos locales. Esto se puede hacer, cuando uno quiera, luego con el comando: .. code-block:: bash $ git merge Estos dos comandos, pueden generalmente reemplazarse por un único comando: .. code-block:: bash $ git pull que realizará la descarga desde el repositorio remoto y la actualización de los archivos locales en un sólo paso. Puntos importantes ------------------ +--------------------------+---------------------------------------------+ | Control de versiones | Historia de cambios y "undo" ilimitado | +--------------------------+---------------------------------------------+ | Configuración | `git config`, con la opción `–global` | +--------------------------+---------------------------------------------+ | Creación | `git init` inicializa el repositorio | +--------------------------+---------------------------------------------+ | | `git clone` copia un repositorio | +--------------------------+---------------------------------------------+ | Modificación | `git status` muestra el estado actual | +--------------------------+---------------------------------------------+ | | `git add` pone archivos bajo control | +--------------------------+---------------------------------------------+ | | `git commit` graba la versión actual | +--------------------------+---------------------------------------------+ | Explorar las versiones | `git log` muestra la historia de cambios | +--------------------------+---------------------------------------------+ | | `git diff` compara versiones | +--------------------------+---------------------------------------------+ | | `git checkout` recupera versiones previas | +--------------------------+---------------------------------------------+ | Comunicación con remotos | `git push` Envía los cambios al remoto | +--------------------------+---------------------------------------------+ | | `git pull` copia los cambios desde remoto | +--------------------------+---------------------------------------------+ Importando módulos ================== Python tiene reglas para la importación de módulos a nuestro código. Un módulo es un archivo cuya extensión es ``.py``. Supongamos que tenemos un módulo ``rotacion_p.py``. 1. En primer lugar, el intérprete busca un módulo denominado ``rotación_p`` dentro de los módulos incorporados automáticamente. La lista de dichos módulos se encuentra usando el método ``sys.builtin_module_names``: .. code:: python import sys sys.builtin_module_names .. code:: python 'rotacion_p' in sys.builtin_module_names 2. En segundo lugar, busca un archivo ``rotacion_p.py`` en una lista de directorios dada por el atributo ``sys.path``. .. code:: python sys.path Este atributo contiene el directorio local (el primero que aparece en la lista de arriba), y una serie de directorios que provienen de - La variable de entorno ``PYTHONPATH`` - Un directorio dependiente de la instalación (en este caso, ’/Users/flavioc/miniconda3/envs/clases/lib/python3.11\`). La manera pythonística de chequear si la variable de entorno ``PYTHONPATH`` existe es utilizar el método ``get`` de ``os.environ``: .. code:: python import os os.environ.get('PYTHONPATH') En nuestro caso no imprime nada, pero de la misma forma se puede setear dicha variable de entorno: .. code:: python os.environ['PYTHONPATH'] = '..' # seteo la variable PYTHONPATH al directorio padre del directorio actual os.environ.get('PYTHONPATH') Por supuesto, todo va a depender de cómo tenemos estructurado nuestro código. En principio, aún cuando uno no utilice completamente las facilidades de Python como lenguaje orientado a objeto, agrupamos funciones que están relacionadas entre sí en distintos módulos. A medida que el código crece, es posible organizar los distintos módulos distribuyéndolos en directorios. Importando módulos de directorios hijos --------------------------------------- Imaginemos que tenemos la siguente estructura de código: :: /miproyecto main.py /lib rotacion.py /graficos simple.py complejo.py /tresd vector.py Es sencillo importar los módulos en los directorios hijos (``lib``, ``graficos``): .. code:: python from graficos.simple import plot_data from graficos.complejo import plot_data_complejo as plot_complejo from graficos.tresd.vector import * from lib.rotacion import rotate Básicamente exponemos el módulo usando el operador ``.`` para ir incorporando los hijos. Por ejemplo, ``graficos.tresd.vector`` refiere al módulo que se encuentra en el archivo ``vector.py`` en el directorio ``graficos.tresd``. Importando módulos desde padres o hermanos ------------------------------------------ Imports relativos ~~~~~~~~~~~~~~~~~ Para importar módulos que están en directorios padres o hermanos (estos últimos son directorios al mismo nivel del directorio desde el cual quiero importar) podemos diferentes estrategias. La primera de ellas es usar la importación de paquetes relativos. Para ello, cada directorio desde el que quiera importar debe poseer un archivo (en principio vacío) denominado ``__init__.py``. Esto permite a Python reconocer los directorios que contienen módulos aún cuando sean padres o hermanos. Veamos ahora la estructura de directorios de ``miproyecto_relativo`` con los archivos ``__init__.py`` agregados: :: /miproyecto main.py __init__.py /lib rotacion.py __init__.py /graficos __init__.py simple.py complejo.py /tresd __init__.py vector.py /tests test_rotacion.py Notemos que ``tests/test_rotacion.py`` tiene también un ``main``, que corre un test: .. code:: python def test_rotacion(): v = np.array([1, 0, 0]) angle = np.array([0, 0, np.pi/2]) assert np.allclose(rotate(angle, v), np.array([0, -1, 0])) if __name__ == "__main__": test_rotacion() print("All tests passed") Esta es una estructura típica de Python, donde tengo tests que prueban funciones en un módulo dado. Notemos cómo se importa el módulo ``rotacion`` desde ``test_rotacion.py``: .. code:: python from ..lib.rotacion import rotate Al igual que con directorios, ``..`` se refiere al directorio padre relativo al directorio actual. Si probamos **desde el directorio** ``miproyecto/tests`` lo siguiente: .. code:: bash python test_rotacion.py Nos encontraremos con el error: .. code:: python ImportError: attempted relative import with no known parent package Para correr el test, tenemos que ir al directorio **padre** del proyecto y correr el main del módulo explícitamente: .. code:: bash python -m miproyecto_relativo.tests.test_rotacion All tests passed Modificando ``sys.path`` ~~~~~~~~~~~~~~~~~~~~~~~~ La forma anterior puede ser engorrosa en el caso en que se tengan muchos módulos en archivos distribuidos en una estructura de directorios complicada. Por otra parte, es posible modificar el atributo ``sys.path`` para incluir el directorio que sea de interés. En este caso, modificamos ``test_rotacion.py``: .. code:: python import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "..")) from lib.rotacion import rotate Entonces, podemos correr desde el directorio ``tests``: .. code:: bash python test_rotacion.py All tests passed o desde su padre como .. code:: bash python -m tests.test_rotacion o .. code:: bash python tests/test_rotacion.py Algunos módulos (biblioteca standard) ====================================== Los módulos pueden pensarse como bibliotecas de objetos (funciones, datos, etc) que pueden usarse según la necesidad. Hay una biblioteca standard con rutinas para muchas operaciones comunes, y además existen muchos paquetes específicos para distintas tareas. Veamos algunos ejemplos: Módulo sys ---------- Este módulo da acceso a variables que usa o mantiene el intérprete Python .. code:: python import sys .. code:: python sys.path .. code:: python sys.getfilesystemencoding() .. code:: python sys.getsizeof(1) .. code:: python sys.getsizeof("hola") .. code:: python help(sys.getsizeof) Vemos que para utilizar las variables (path) o funciones (getsizeof) debemos referirlo anteponiendo el módulo en el cuál está definido (sys) y separado por un punto. Cuando hacemos un programa, con definición de variables y funciones. Podemos utilizarlo como un módulo, de la misma manera que los que ya vienen definidos en la biblioteca standard o en los paquetes que instalamos. Módulo ``os`` ------------- El módulo ``os`` tiene utilidades para operar sobre nombres de archivos y directorios de manera segura y portable, de manera que pueda utilizarse en distintos sistemas operativos. Vamos a ver ejemplos de uso de algunas facilidades que brinda: .. code:: python import os print(os.curdir) print(os.pardir) print (os.getcwd()) .. code:: python cur = os.getcwd() par = os.path.abspath("..") print(cur) print(par) .. code:: python print(os.path.abspath(os.curdir)) print(os.getcwd()) .. code:: python print(os.path.basename(cur)) print(os.path.splitdrive(cur)) # Útil en Windows donde hay letras que identifican las unidades de disco .. code:: python print(os.path.commonprefix((cur, par))) archivo = os.path.join(par,'este' , 'otro.dat') print (archivo) print (os.path.split(archivo)) print (os.path.splitext(archivo)) print (os.path.exists(archivo)) print (os.path.exists(cur)) Como es aparente de estos ejemplos, se puede acceder a todos los objetos (funciones, variables) de un módulo utilizando simplemente la línea ``import `` pero puede ser tedioso escribir todo con prefijos (como ``os.path.abspath``) por lo que existen dos alternativas que pueden ser más convenientes. La primera corresponde a importar todas las definiciones de un módulo en forma implícita: .. code:: python from os import * Después de esta declaración usamos los objetos de la misma manera que antes pero obviando la parte de ``os.`` .. code:: python path.abspath(curdir) Esto es conveniente en algunos casos pero no suele ser una buena idea en programas largos ya que distintos módulos pueden definir el mismo nombre, y se pierde información sobre su origen. Una alternativa que es conveniente y permite mantener mejor control es importar explícitamente lo que vamos a usar: .. code:: python from os import curdir, pardir, getcwd from os.path import abspath print(abspath(pardir)) print(abspath(curdir)) print(abspath(getcwd())) Además podemos darle un nombre diferente al importar módulos u objetos .. code:: python import os.path as path from os import getenv as ge .. code:: python help(ge) .. code:: python ge('HOME') .. code:: python path.realpath(curdir) Acá hemos importado el módulo ``os.path`` (es un sub-módulo) como ``path`` y la función ``getenv`` del módulo ``os`` y la hemos renombrado ``ge``. .. code:: python curdir .. code:: python [a for a in os.walk(curdir)] .. code:: python help(os.walk) .. code:: python import os from os.path import join, getsize for root, dirs, files in os.walk('./'): print(root, "consume ", end="") print(sum([getsize(join(root, name)) for name in files])/1024, end="") print(" kbytes en ", len(files), "non-directory files") if '.ipynb_checkpoints' in dirs: dirs.remove('.ipynb_checkpoints') # don't visit CVS directories Módulo ``glob`` --------------- El módulo ``glob`` encuentra nombres de archivos (o directorios) utilizando patrones similares a los de la consola. La función más utilizada es ``glob.glob()`` Veamos algunos ejemplos de uso: .. code:: python import glob .. code:: python nb_clase4= glob.glob('04*.ipynb') .. code:: python nb_clase4 .. code:: python nb_clase4.sort() .. code:: python nb_clase4 .. code:: python nb_clases1a4 = glob.glob('0[0-4]*.ipynb') .. code:: python nb_clases1a4.sort() .. code:: python for f in sorted(nb_clases1a4): print('Clase en archivo {}'.format(f)) Módulo ``Argparse`` ------------------- Este módulo tiene lo necesario para hacer rápidamente un programa para utilizar por línea de comandos, aceptando todo tipo de argumentos y dando información sobre su uso. .. code:: python import argparse VERSION = 1.0 parser = argparse.ArgumentParser( description='"Mi programa que acepta argumentos por línea de comandos"') parser.add_argument('-V', '--version', action='version', version='%(prog)s version {}'.format(VERSION)) parser.add_argument('-n', '--entero', action=store, dest='n', default=1) args = parser.parse_args() Más información en la `biblioteca standard `__ y en `Argparse en Python Module of the week `__ Módulo ``re`` ------------- Este módulo provee la infraestructura para trabajar con *regular expressions*, es decir para encontrar expresiones que verifican “cierta forma general”. Veamos algunos conceptos básicos y casos más comunes de uso. Búsqueda de un patrón en un texto ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Empecemos con un ejemplo bastante común. Para encontrar un patrón en un texto podemos utilizar el método ``search()`` .. code:: python import re .. code:: python busca = 'un' texto = 'Otra vez vamos a usar "Hola Mundo"' match = re.search(busca, texto) print('Encontré "{}"\nen:\n "{}"'.format(match.re.pattern, match.string)) print('En las posiciones {} a {}'.format(match.start(), match.end())) .. parsed-literal:: Encontré "un" en: "Otra vez vamos a usar "Hola Mundo"" En las posiciones 29 a 31 Acá buscamos una expresión (el substring “un”). Esto es útil pero no muy diferente a utilizar los métodos de strings. Veamos como se definen los patrones. Definición de expresiones ~~~~~~~~~~~~~~~~~~~~~~~~~ Vamos a buscar un patrón en un texto. Veamos cómo se definen los patrones a buscar. - La mayoría de los caracteres se identifican consigo mismo (si quiero encontrar “gato”, uso como patrón “gato”) - Hay unos pocos caracteres especiales (metacaracteres) que tienen un significado especial, estos son: :: . ^ $ * + ? { } [ ] \ | ( ) - Si queremos encontrar uno de los metacaracteres, tenemos que precederlos de ``\``. Por ejemplo si queremos encontrar un corchete usamos ``\[`` - Los corchetes “[” y ”]” se usan para definir una clase de caracteres, que es un conjunto de caracteres que uno quiere encontrar. - Los caracteres a encontrar se pueden dar individualmente. Por ejemplo ``[gato]`` encontrará cualquiera de ``g``, ``a``, ``t``, ``o``. - Un rango de caracteres se puede dar dando dos caracteres separados por un guión. Por ejemplo ``[a-z]`` dará cualquier letra entre “a” y “z”. Similarmente ``[0-5][0-9]`` dará cualquier número entre “00” y “59”. - Los metacaracteres pierden su significado especial dentro de los corchetes. Por ejemplo ``[.*)]`` encontrará cualquiera de “.”, “\*“,”)“. - El punto ``.`` indica *cualquier caracter* - Los símbolos ``*``, ``+``, ``?`` indican repetición: - ``?``: Indica 0 o 1 aparición de lo anterior - ``*``: Indica 0 o más apariciones de lo anterior - ``+``: Indica 1 o más apariciones de lo anterior - Para encontrar una cantidad determinada de caracteres, se puede agregar dicha cantidad entre llaves ``{}``. Por ejemplo, ``[a-z]{3}`` resultará en cualquier string de exactamente tres letras minúsculas. .. code:: python busca = "[a-z]+@[a-z]+\.[a-z]+" # Un patrón para buscar direcciones de email texto = "nombre@server.com, apellido@server1.com, nombre1995@server.com, UnNombreyApellido, nombre.apellido82@servidor.com.ar, Nombre.Apellido82@servidor.com.ar".split(',') print(texto,'\n') for direc in texto: m= re.search(busca, direc) print('Para la línea:', direc) if m is None: print(' No encontré dirección de correo!') else: print(' Encontré la dirección de correo:', m.string) .. parsed-literal:: ['nombre@server.com', ' apellido@server1.com', ' nombre1995@server.com', ' UnNombreyApellido', ' nombre.apellido82@servidor.com.ar', ' Nombre.Apellido82@servidor.com.ar'] Para la línea: nombre@server.com Encontré la dirección de correo: nombre@server.com Para la línea: apellido@server1.com No encontré dirección de correo! Para la línea: nombre1995@server.com No encontré dirección de correo! Para la línea: UnNombreyApellido No encontré dirección de correo! Para la línea: nombre.apellido82@servidor.com.ar No encontré dirección de correo! Para la línea: Nombre.Apellido82@servidor.com.ar No encontré dirección de correo! .. parsed-literal:: <>:1: SyntaxWarning: invalid escape sequence '\.' <>:1: SyntaxWarning: invalid escape sequence '\.' /var/folders/lw/b464kt25373b__wh0bxy280r0000gn/T/ipykernel_50240/497915755.py:1: SyntaxWarning: invalid escape sequence '\.' busca = "[a-z]+@[a-z]+\.[a-z]+" # Un patrón para buscar direcciones de email - Acá la expresión ``[a-z]`` significa todos los caracteres en el rango “a” hasta “z”. - ``[a-z]+`` significa cualquier secuencia de una letra o más. - Los corchetes también se pueden usar en la forma ``[abc]`` y entonces encuentra *cualquiera* de ``a``, ``b``, o ``c``. Vemos que no encontró todas las direcciones posibles. Porque el patrón no está bien diseñado. Un poco mejor sería: .. code:: python busca = "[a-zA-Z0-9.]+@[a-z.]+" # Un patrón para buscar direcciones de email print(texto,'\n') for direc in texto: m= re.search(busca, direc) print('Para la línea:', direc) if m is None: print(' No encontré dirección de correo:') else: print(' Encontré la dirección de correo:', m.group()) .. parsed-literal:: ['nombre@server.com', ' apellido@server1.com', ' nombre1995@server.com', ' UnNombreyApellido', ' nombre.apellido82@servidor.com.ar', ' Nombre.Apellido82@servidor.com.ar'] Para la línea: nombre@server.com Encontré la dirección de correo: nombre@server.com Para la línea: apellido@server1.com Encontré la dirección de correo: apellido@server Para la línea: nombre1995@server.com Encontré la dirección de correo: nombre1995@server.com Para la línea: UnNombreyApellido No encontré dirección de correo: Para la línea: nombre.apellido82@servidor.com.ar Encontré la dirección de correo: nombre.apellido82@servidor.com.ar Para la línea: Nombre.Apellido82@servidor.com.ar Encontré la dirección de correo: Nombre.Apellido82@servidor.com.ar Los metacaracteres no se activan dentro de clases (adentro de corchetes). En el ejemplo anterior el punto ``.`` actúa como un punto y no como un metacaracter. En este caso, la primera parte: ``[a-zA-Z0-9.]+`` significa: “Encontrar cualquier letra minúscula, mayúscula, número o punto, una o más veces cualquiera de ellos” Repetición de un patrón ~~~~~~~~~~~~~~~~~~~~~~~ Si queremos encontrar strings que presentan la secuencia una o más veces podemos usar ``findall()`` que devuelve todas las ocurrencias del patrón que no se superponen. Por ejemplo: .. code:: python texto = 'abbaaabbbbaaaaa' busca = 'ab' mm = re.findall(busca, texto) print(mm) print(type(mm[0])) for m in mm: print('Encontré {}'.format(m)) .. parsed-literal:: ['ab', 'ab'] Encontré ab Encontré ab .. code:: python p = re.compile('abc*') m= p.findall('acholaboy') print(m) m= p.findall('acholabcoynd sabcccs slabc labdc abc') print(m) .. parsed-literal:: ['ab'] ['abc', 'abccc', 'abc', 'ab', 'abc'] Si va a utilizar expresiones regulares es recomendable que lea más información en la `biblioteca standard `__, en `el HOWTO `__, en `Python Module of the week `__ o `acá `__. Para practicar RegEx, `ésta es una buena página `__. Si efectivamente tiene que diseñar una expresión regular, `esta página puede ser útil `__. Ejercicios 09 ============= 1. **PARA ENTREGAR** En el ejercicio 08 se creó la clase ``Materia`` que describa una materia que se dicta en el IB. En dicha clase también se usaron las clases ``Persona`` y ``Estudiante``. La clase ``Materia`` provee los siguientes métodos: - ``agrega_estudiante`` que agrega un estudiante al curso - ``agrega_docente`` que agrega un docente al curso - ``imprime_estudiantes`` que lista los estudiantes del curso Vamos a asumir que los estudiantes son alumnos de grado, de modo tal que las carreras posibles son Ingeniería (Nuclear, Mecánica o Telecomunicaciones) o Física, o son estudiantes vocacionales. En este ejercicio le pedimos que: - Corrija la clase ``Estudiante`` de forma tal que sólo se puedan registrar estudiantes de grado de las carreras mencionadas, o estudiantes vocacionales. - Extienda la clase ``Materia`` de modo tal que se pueda registrar la nota de cada estudiante al finalizar el curso. - Cree la clase ``Admin`` que permita imprimir los listados de estudiantes de una materia y pueda expedir certificados de aprobación que contengan el nombre del alumno, el nombre de la materia y la nota obtenida en números y en letras. (El certificado es un archivo de texto con la información mencionada). - Finalmente, modifique las clases anteriores de forma tal que se puedan crear y manejar materias a través de distintos años. Organice el código en distintos módulos dentro de un proyecto, con un ``main.py`` donde se utilicen, a modo de ejemplo, las distintas características del código solicitadas. Para entregar el ejercicio, cree un repositorio privado en GitHub conteniendo el proyecto con el nombre ‘modelo-materias-ib’ y agregue como colaborador a ``cursopythonib``. 2. Un ejercicio interesante de aplicación de expresiones regulares(\*) consiste en encontrar los coeficientes y las potencias de un polinomio que viene descripto como un string: .. code:: python polinomio = "5x^4 + 3x^2 - 2x + 7" .. (\*) Este no es un ejercicio fácil, y además, para extraer la información requerida, es necesario poder capturar `grupos de expresiones regulares `__. -------------- .