Clase 9: Control de versiones y módulos
Nota
Esta clase está 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:

o, más gracioso pero igualmente común, esta otra:

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)

y después estos son compatibilizados e incorporados en un único documento.

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:
$ git <comando> [opciones]
Por ejemplo, para obtener ayuda directamente desde el programa, se puede utilizar cualquiera de las opciones:
$ 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:
$ git config --help
$ git help config
Configuración básica
Una vez que está instalado, es conveniente configurarlo desde una terminal, con los comandos:
$ 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:
$ 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:
$ 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:
$ 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:
$ git clone <url-del-repositorio> [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:
$ 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:
$ 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:
$ git add <nuevo-archivo>
$ git add <archivo-modificado>
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»
$ 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:
$ 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):
$ 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:
$ git merge
Estos dos comandos, pueden generalmente reemplazarse por un único comando:
$ 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
.
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étodosys.builtin_module_names
:
import sys
sys.builtin_module_names
'rotacion_p' in sys.builtin_module_names
En segundo lugar, busca un archivo
rotacion_p.py
en una lista de directorios dada por el atributosys.path
.
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
:
import os
os.environ.get('PYTHONPATH')
En nuestro caso no imprime nada, pero de la misma forma se puede setear dicha variable de entorno:
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
):
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:
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
:
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:
python test_rotacion.py
Nos encontraremos con el error:
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:
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
:
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
:
python test_rotacion.py
All tests passed
o desde su padre como
python -m tests.test_rotacion
o
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
import sys
sys.path
sys.getfilesystemencoding()
sys.getsizeof(1)
sys.getsizeof("hola")
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:
import os
print(os.curdir)
print(os.pardir)
print (os.getcwd())
cur = os.getcwd()
par = os.path.abspath("..")
print(cur)
print(par)
print(os.path.abspath(os.curdir))
print(os.getcwd())
print(os.path.basename(cur))
print(os.path.splitdrive(cur)) # Útil en Windows donde hay letras que identifican las unidades de disco
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 <modulo>
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:
from os import *
Después de esta declaración usamos los objetos de la misma manera que
antes pero obviando la parte de os.
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:
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
import os.path as path
from os import getenv as ge
help(ge)
ge('HOME')
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
.
curdir
[a for a in os.walk(curdir)]
help(os.walk)
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:
import glob
nb_clase4= glob.glob('04*.ipynb')
nb_clase4
nb_clase4.sort()
nb_clase4
nb_clases1a4 = glob.glob('0[0-4]*.ipynb')
nb_clases1a4.sort()
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.
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()
import re
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()))
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 deg
,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 caracterLos 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.
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)
['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!
<>: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 dea
,b
, oc
.
Vemos que no encontró todas las direcciones posibles. Porque el patrón no está bien diseñado. Un poco mejor sería:
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())
['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:
texto = 'abbaaabbbbaaaaa'
busca = 'ab'
mm = re.findall(busca, texto)
print(mm)
print(type(mm[0]))
for m in mm:
print('Encontré {}'.format(m))
['ab', 'ab']
<class 'str'>
Encontré ab
Encontré ab
p = re.compile('abc*')
m= p.findall('acholaboy')
print(m)
m= p.findall('acholabcoynd sabcccs slabc labdc abc')
print(m)
['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
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 cursoagrega_docente
que agrega un docente al cursoimprime_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
.
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:
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.
.