<jupyter_start><jupyter_text><!--03/11Archivos binarios. Pickles.Apareo de archivos secuenciales.CLASE DE LABORATORIO (Gonzalo)-->  ArchivosPor lo general, cuando se trabaja con un archivo se hacen tres operaciones seguidas:1. Abrir el archivo2. Procesar el archivo3. Cerrar el archivoY hay que tener cuidado, porque si ocurre algún error con el archivo en algún punto de su procesamiento es necesario encargarse de cerrarlo, antes de que la excepción siga subiendo niveles. Trabajando con archivo de una forma seguraPara trabajar con los archivos de una forma más simple es que se agregó la sentencia **with** que define un contexto dentro del cual nos asegura que, ocurra una excepción o no, el archivo se cerrará al momento de salir de ese contexto:<jupyter_code>try:
    with open('ejemplo.txt') as fd:
        print '¿El archivo se encuentra cerrado?', fd.closed
        a += 2  # Como la variable a no existe, va a tirar
                # una excepción del tipo NameError
        print 'Estas líneas nunca se van a mostrar porque'
        print 'antes va a ocurrir un error'
except NameError:
    print 'Ocurrio un error'
    
print '¿El archivo se encuentra cerrado?', fd.closed
with open('ejemplo.txt', 'r') as archivo:
    print '¿El archivo se encuentra cerrado?: {}'.format(archivo.closed)
    print
    for linea in archivo:
        longitud = len(linea[:-1])
        print '{:2}: {}'.format(longitud, linea[:-1])

print
print '¿El archivo se encuentra cerrado?: {}'.format(archivo.closed)<jupyter_output>¿El archivo se encuentra cerrado?: False

70: Python was created in the early 1990s by <NAME> at Stichting
68: Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
64: as a successor of a language called ABC.  Guido remains Python's
70: principal author, although it includes many contributions from others.
 0: 
66: In 1995, Guido continued his work on Python at the Corporation for
70: National Research Initiatives (CNRI, see http://www.cnri.reston.va.us)
61: in Reston, Virginia where he released several versions of the
 9: software.
 0: 
64: In May 2000, Guido and the Python core development team moved to
70: BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
62: year, the PythonLabs team moved to Digital Creations (now Zope
68: Corporation, see http://www.zope.com).  In 2001, the Python Software
62: Foundation (PSF, see http://www.python.org/psf/) was formed, a
66: non-profit organization created specifically to own Python-related
66: Inte[...]<jupyter_text>Si bien no cerramos explícitamente el archivo usando la función close, al salir del bloque de código que encierra el with el archivo se encontrará cerrado.  PicklesLos [*pickles*](https://docs.python.org/2/library/pickle.htmlmodule-pickle) son una forma de guardar estructuras de datos complejas y recuperarlas fácilmente, sin necesidad de convertirlas a texto y luego parsearlas: Ejemplo 1: Guardar de a un elementoSe puede usar los pickles como se hacía con los viejos archivos de Pascal, donde se guardaba un registro detrás del otro; pero con la diferencia de que en este caso no es necesario que todos los registros sean del mismo tipo:<jupyter_code>import pickle  # Importo la biblioteca necesaria

# Creo la variable archivo
with open('ejemplo.pkl', 'wb') as archivo:
    pkl = pickle.Pickler(archivo)  # Creo mi punto de acceso a los datos a partir del archivo

    lista1 = [1, 2, 3]
    lista2 = [4, 5]
    diccionario = {'campo1': 1, 'campo2': 'dos'}

    pkl.dump(lista1)         # Guardo la lista1 de [1, 2, 3]
    pkl.dump(None)           # Guardo el valor None
    pkl.dump(lista2)
    pkl.dump('H<NAME>')
    pkl.dump('')
    pkl.dump(diccionario)
    pkl.dump(1)<jupyter_output><empty_output><jupyter_text>Para leer de un archivo pickle no puedo usar el método readline que usa la estructura for, por lo que no me queda otra que siempre intentar leer hasta que lance una excepción del tipo *EOFError*:<jupyter_code>import pickle
with open('ejemplo.pkl', 'rb') as archivo:
    print pickle.load(archivo)  # lista1
    print pickle.load(archivo)  # None
    print pickle.load(archivo)  # lista2
    print pickle.load(archivo)  # H<NAME>
    print pickle.load(archivo)  # ''
    print pickle.load(archivo)  # diccionario
    print pickle.load(archivo)  # 1
    print pickle.load(archivo)  # Fin de archivo
    
with open('ejemplo.pkl', 'rb') as archivo:
    seguir_leyendo = True
    while seguir_leyendo:
        try:
            dato = pickle.load(archivo)  # Leo del archivo un elemento
            print dato
        except EOFError:
            seguir_leyendo = False<jupyter_output>[1, 2, 3]
None
[4, 5]
Hola mundo

{'campo1': 1, 'campo2': 'dos'}
1<jupyter_text>Ejemplo 2: Guardo una lista de elementosAsí como en el ejemplo anterior guardamos de a un elemento por vez, también podríamos guardar una lista completa que tenga todos los elementos en memoria. De ésta forma, los archivos podrían usarse para cargar los datos al comenzar el programa y guardarlos todos juntos antes de terminar. Suponiendo que estoy desarrollando un juego en que no van a haber muchos jugadores compitiendo entre si, podría tener una lista con los puntajes y hacer:<jupyter_code>lista = [  # Creo la lista que quiero guardar
    {'usuario': 'csanchez', 'puntaje': 5}, 
    {'usuario': 'pperez', 'puntaje': 3}, 
    {'usuario': 'jromero', 'puntaje': 1}, 
]

# Guardo la lista en el archiv
with open('ejemplo_2.pkl', 'wb') as archivo:
    pkl = pickle.Pickler(archivo)
    pkl.dump(lista)<jupyter_output><empty_output><jupyter_text>Y si ahora quiero sumarle 3 puntos a un usuario en particular tendría que:1. Leer todo el archivo en una lista2. Buscar el usuario y actualizarle los puntos3. Guardar toda la lista en el archivo<jupyter_code>def imprimir_puntajes(lista_puntajes):
    for puntaje in lista_puntajes:
        print '    ', puntaje


# Leo del archivo
with open('ejemplo_2.pkl', 'rb') as archivo:
    lista_puntajes = pickle.load(archivo)


# Actualizo el puntaje
print 'La lista antes de hacer el cambio es:'
imprimir_puntajes(lista_puntajes)

pos =  0
lista_puntajes[pos]['puntaje'] += 3

print 'La lista una vez hecho el cambio es:'
imprimir_puntajes(lista_puntajes)

# Guardo la lista en el archiv
with open('ejemplo_2.pkl', 'wb') as archivo:
    pkl = pickle.Pickler(archivo)
    pkl.dump(lista_puntajes)<jupyter_output>La lista antes de hacer el cambio es:
     {'puntaje': 5, 'usuario': 'csanchez'}
     {'puntaje': 3, 'usuario': 'pperez'}
     {'puntaje': 1, 'usuario': 'jromero'}
La lista una vez hecho el cambio es:
     {'puntaje': 8, 'usuario': 'csanchez'}
     {'puntaje': 3, 'usuario': 'pperez'}
     {'puntaje': 1, 'usuario': 'jromero'}<jupyter_text>Si bien es muy práctica esta alternativa, tiene el gran inconveniente de no hacer un uso eficiente de la memoria. Si el archivo contiene millones de usuarios, los estaríamos levantando todos a memoria, con el gran costo que tiene eso (no sólo en espacio, sino también en tiempo) con el único objetivo de sumarle 3 puntos a un único usuario. Y una vez que actualizamos el puntaje de ese usuario, tendríamos que volver a guardar todo el archivo en el disco.  Abstrayendonos del uso de los picklesSi bien el uso de los pickles puede resultar muy útil, la forma de leer la información guardada en ellos no suele ser muy cómoda. Por lo que podríamos implementar en un archivo `utils.py` las siguientes dos funciones para abstraernos un poco de cómo se accede a los datos:```Python encoding: utf8import pickledef guardar_en_archivo(archivo, contenido):    """Guarda lo que le pasen como segundo parámetro en el archivo     que recibe como primer parámetro.    El parámetro llamado archivo tiene que estar abieto en modo     binario y para escritura (wb)    """    pickler = pickle.Pickler(archivo)    pickler.dump(contenido)def leer_desde_archivo(archivo, valor_por_defecto=None):    """Lee del archivo archivo un registro y lo retorna junto con una    variable booleana que indica si llegó al fin de archivo o no.    El parámetro llamado archivo tiene que estar abieto en modo     binario y para lectura (rb).    Si se intenta leer más allá del fin de archivo, data valdrá lo que le     hayan pasado en valor_por_defecto (si no le pasan nada será None)    y fin_de_archivo será True. En cualquier otro caso fin_de_archivo    será False.    """    try:        data = pickle.load(archivo)        fin_de_archivo = False    except EOFError:        data = valor_por_defecto        fin_de_archivo = True    return data, fin_de_archivo```En este caso, se podría considerar que la función `leer_desde_archivo` funciona similar a cómo lo hacen los archivos con un registro centinella. Por lo que podríamos usar:<jupyter_code>import utils


curso = [
    {'nombre': '<NAME>', 'nota': 8, 'padron': 90431, 'grupo': 1},
    {'nombre': '<NAME>', 'nota': 2, 'padron': 92953, 'grupo': 1},
    {'nombre': '<NAME>', 'nota': 10, 'padron': 92407, 'grupo': 1},
    {'nombre': '<NAME>', 'nota': 9, 'padron': 96556, 'grupo': 2},
    {'nombre': '<NAME>', 'nota': 7, 'padron': 92143, 'grupo': 2},
    {'nombre': '<NAME>', 'nota': 9, 'padron': 92431, 'grupo': 2},
    {'nombre': '<NAME>', 'nota': 4, 'padron': 98306, 'grupo': 3},
    {'nombre': '<NAME>', 'nota': 8, 'padron': 97972, 'grupo': 3},
    {'nombre': '<NAME>', 'nota': 2, 'padron': 93108, 'grupo': 4},
    {'nombre': '<NAME>', 'nota': 5, 'padron': 96739, 'grupo': 5},
]

print 'Creo el archivo vacío usando el modo "wb"'
print 'Si tenía algo, ya lo borre...'
MAX = {'padron': 999999999999}
with open('curso.pkl', 'wb') as archivo:
    for alumno in curso:
        print 'Guardando el alumno {} en el archivo'.format(alumno)
        utils.guardar_en_archivo(archivo, alumno)

print
print 'Abro el archivo en modo lectura...'
with open('curso.pkl', 'rb') as archivo:
    alumno, fin_de_archivo = utils.leer_desde_archivo(archivo)
    while not fin_de_archivo:
        print 'Leyendo el alumno {} en el archivo'.format(alumno)
        alumno, fin_de_archivo = utils.leer_desde_archivo(archivo)<jupyter_output>Creo el archivo vacío usando el modo "wb"
Si tenía algo, ya lo borre...
Guardando el alumno {'nombre': '<NAME>', 'grupo': 1, 'nota': 8, 'padron': 90431} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 1, 'nota': 2, 'padron': 92953} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 1, 'nota': 10, 'padron': 92407} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 2, 'nota': 9, 'padron': 96556} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 2, 'nota': 7, 'padron': 92143} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 2, 'nota': 9, 'padron': 92431} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 3, 'nota': 4, 'padron': 98306} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 3, 'nota': 8, 'padron': 97972} en el archivo
Guardando el alumno {'nombre': '<NAME>', 'grupo': 4, 'nota': 2, 'padron': 93108} en el archivo
Gu[...]<jupyter_text>Cortes de controlCuando estamos hablando de archivos y nos referimos a **_corte de control_** estamos haciendo referencia al algoritmo que toma un archivo ordenado, por una o más claves, y como resultado del mismo nos devuelve un _"resumen"_ del mismo. Por ejemplo, si tenemos el siguiente archivo ordenado por código de cliente:**Código de cliente** | **Número de factura** | **Monto de la factura**-------| ---------- |  ------------001 | 2020452 | 916002 | 12069115 | 772002 | 14534467 | 264002 | 1424980 | 752002 | 16214863 | 424003 | 6882583 | 590003 | 18817277 | 654003 | 1944327 | 211003 | 16837776 | 595003 | 10145610 | 444004 | 4671025 | 393004 | 13453769 | 556005 | 7126081 | 35005 | 16497082 | 367Y queremos calcular cuánta plata gastó cada cliente en nuestro negocio:```El cliente 001 gastó  916El cliente 002 gastó 2212El cliente 003 gastó 2494El cliente 004 gastó  949El cliente 005 gastó  402```Podríamos usar el siguiente algoritmo para generar el reporte:<jupyter_code>import utils


def crear_archivo_de_ventas():
    ventas = [
        {'cliente': '001', 'nro_factura': 2020452, 'monto': 916},
        {'cliente': '002', 'nro_factura': 12069115, 'monto': 772},
        {'cliente': '002', 'nro_factura': 14534467, 'monto': 264},
        {'cliente': '002', 'nro_factura': 1424980, 'monto': 752},
        {'cliente': '002', 'nro_factura': 16214863, 'monto': 424},
        {'cliente': '003', 'nro_factura': 6882583, 'monto': 590},
        {'cliente': '003', 'nro_factura': 18817277, 'monto': 654},
        {'cliente': '003', 'nro_factura': 1944327, 'monto': 211},
        {'cliente': '003', 'nro_factura': 16837776, 'monto': 595},
        {'cliente': '003', 'nro_factura': 10145610, 'monto': 444},
        {'cliente': '004', 'nro_factura': 4671025, 'monto': 393},
        {'cliente': '004', 'nro_factura': 13453769, 'monto': 556},
        {'cliente': '005', 'nro_factura': 7126081, 'monto': 35},
        {'cliente': '005', 'nro_factura': 16497082, 'monto': 367}
    ]

    print 'Creo el archivo vacío usando el modo "wb"'
    with open('ventas.pkl', 'wb') as archivo:
        for venta in ventas:
            utils.guardar_en_archivo(archivo, venta)
            
            
def mostrar_ventas_por_cliente(archivo):
    valor_por_defecto = {'cliente': None, 'monto':0}
    total = 0
    # Leo el primer registro del archivo
    venta, fin_de_archivo = utils.leer_desde_archivo(archivo, valor_por_defecto)
    codigo_cliente = venta['cliente']
    while not fin_de_archivo:
        codigo_cliente = venta['cliente']
        subtotal = 0  # Inicializo las ventas de este cliente
        # Mientras siga procesando el mismo cliente...
        while venta['cliente'] == codigo_cliente:
            total += venta['monto']  # Acumulo las ventas totales
            subtotal += venta['monto']  # Acumulo las ventas de este cliente
            venta, fin_de_archivo = utils.leer_desde_archivo(archivo, valor_por_defecto)

        print '      El cliente {cliente} gastó {monto:4}'.format(cliente=codigo_cliente, monto=subtotal)

    print 'El total es de ${}'.format(total)
    

crear_archivo_de_ventas()
print 'Abro el archivo en modo lectura...'

# Abro el archivo usando el with para asegurarme 
# que, pase lo que pase, el archivo quede cerrado
with open('ventas.pkl', 'rb') as archivo:
    mostrar_ventas_por_cliente(archivo)<jupyter_output>Creo el archivo vacío usando el modo "wb"
Abro el archivo en modo lectura...
      El cliente 001 gastó  916
      El cliente 002 gastó 2212
      El cliente 003 gastó 2494
      El cliente 004 gastó  949
      El cliente 005 gastó  402
El total es de $6973<jupyter_text>_¿Y si el archivo fuera de texto?_ Es simple, tratamos de llevar el problema a la solución que conocemos. Para eso podríamos crearnos una función que se comporte de una forma similar a la que se encuentra en _utils_:```Pythondef leer_desde_archivo(archivo, valor_por_defecto):    try:        linea = archivo.readline()        codigo_cliente, factura, monto = linea.strip().split(',')        data = {            'cliente': codigo_cliente,            'factura': int(factura),            'monto': int(monto)        }        fin_de_archivo = False    except StopIteration:        data = valor_por_defecto        fin_de_archivo = True        return data, fin_de_archivo```Esta función, no sólo lee cada línea, sino que una vez leída:1. usa de la función `strip` para quitar el `\n` que tiene al final de la línea1. usa la función `split` para separar el string por comas1. hace uso del _unpacking_ para guardar en 3 variables distintas cada uno de los campos de la línea1. crea un diccionario con cada uno de los datos que obtuvo de la línea, pero antes, convierte el número de factura y el monto a entero usando la función `int` Por último, si ya habíamos llegado al final del archivo e intentamos leer de nuevo, el intérprete va a lanzar la excepción `StopIteration` que la capturamos con el `try-except` y, en ese caso, devolvemos el valor que nos pasaron por parámetro. Entonces, después el algoritmo nos queda igual, a excepción de que ahora no importamos a la utils y la forma de crear y abrir el archivo va a ser distinta:<jupyter_code>def leer_desde_archivo(archivo, valor_por_defecto):
    try:
        linea = archivo.readline()
        codigo_cliente, factura, monto = linea.strip().split(',')
        data = {
            'cliente': codigo_cliente,
            'factura': int(factura),
            'monto': int(monto)
        }
        fin_de_archivo = False
    except Exception:
        data = valor_por_defecto
        fin_de_archivo = True
    
    return data, fin_de_archivo


def crear_archivo_de_ventas():
    ventas = """001,2020452,916
002,12069115,772
002,14534467,264
002,1424980,752
002,16214863,424
003,6882583,590
003,18817277,654
003,1944327,211
003,16837776,595
003,10145610,444
004,4671025,393
004,13453769,556
005,7126081,35
005,16497082,367
"""
    print 'Creo el archivo vacío usando el modo "wt"'
    with open('ventas.csv', 'wt') as archivo:
        archivo.write(ventas)
            
            
def mostrar_ventas_por_cliente(archivo):
    valor_por_defecto = {'cliente': None, 'monto':0}
    total = 0
    # Leo el primer registro del archivo
    venta, fin_de_archivo = leer_desde_archivo(archivo, valor_por_defecto)
    codigo_cliente = venta['cliente']
    while not fin_de_archivo:
        codigo_cliente = venta['cliente']
        subtotal = 0  # Inicializo las ventas de este cliente
        # Mientras siga procesando el mismo cliente...
        while venta['cliente'] == codigo_cliente:
            total += venta['monto']  # Acumulo las ventas totales
            subtotal += venta['monto']  # Acumulo las ventas de este cliente
            venta, fin_de_archivo = leer_desde_archivo(archivo, valor_por_defecto)

        print '      El cliente {cliente} gastó {monto:4}'.format(cliente=codigo_cliente, monto=subtotal)

    print 'El total es de ${}'.format(total)
    

crear_archivo_de_ventas()
print 'Abro el archivo en modo lectura...'
# Abro el archivo usando el with para asegurarme 
# que, pase lo que pase, el archivo quede cerrado
with open('ventas.csv', 'rt') as archivo:
    mostrar_ventas_por_cliente(archivo)<jupyter_output>Creo el archivo vacío usando el modo "wt"
Abro el archivo en modo lectura...
      El cliente 001 gastó  916
      El cliente 002 gastó 2212
      El cliente 003 gastó 2494
      El cliente 004 gastó  949
      El cliente 005 gastó  402
El total es de $6973<jupyter_text>Merge de archivosEl merge, o apareo, de archivos consiste en tener dos o más archivos que se encuentran ordenados por una misma clave y se quieren procesar leyéndolos una única vez y generar un reporte o un nuevo archivo con dicha información consolidada.Supongamos que tenemos el siguiente código para crear unos archivos de prueba:<jupyter_code>import utils
oper = [
    {'cta': 1, 'imp': 800},
    {'cta': 1, 'imp': 250},
    {'cta': 2, 'imp': 700},
    {'cta': 2, 'imp': 700},
    {'cta': 10, 'imp': 1000},
]
with open('movs1.pkl', 'wb') as archivo:
    for movimiento in oper:
        print 'Guardando la operacion {} en el archivo movs1.pkl'.format(movimiento)
        utils.guardar_en_archivo(archivo, movimiento)

print

operaciones_2 = [
    {'cta': 1, 'imp': 800},
    {'cta': 2, 'imp': 700},
    {'cta': 3, 'imp': 700},
    {'cta': 10, 'imp': 100},
    {'cta': 15, 'imp': 3},
]
with open('movs2.pkl', 'wb') as archivo:
    for movimiento in operaciones_2:
        print 'Guardando la operacion {} en el archivo movs2.pkl'.format(movimiento)
        utils.guardar_en_archivo(archivo, movimiento)<jupyter_output>Guardando la operacion {'imp': 800, 'cta': 1} en el archivo movs1.pkl
Guardando la operacion {'imp': 250, 'cta': 1} en el archivo movs1.pkl
Guardando la operacion {'imp': 700, 'cta': 2} en el archivo movs1.pkl
Guardando la operacion {'imp': 700, 'cta': 2} en el archivo movs1.pkl
Guardando la operacion {'imp': 1000, 'cta': 10} en el archivo movs1.pkl

Guardando la operacion {'imp': 800, 'cta': 1} en el archivo movs2.pkl
Guardando la operacion {'imp': 700, 'cta': 2} en el archivo movs2.pkl
Guardando la operacion {'imp': 700, 'cta': 3} en el archivo movs2.pkl
Guardando la operacion {'imp': 100, 'cta': 10} en el archivo movs2.pkl
Guardando la operacion {'imp': 3, 'cta': 15} en el archivo movs2.pkl<jupyter_text>Entonces si lo que quiero es mostrar el estado de cada una de estas cuentas podría hacer:<jupyter_code>import utils

with open('movs1.pkl', 'rb')as movs1, open('movs2.pkl', 'rb') as movs2:
    MAX = {'cta': 99999999, 'imp':0}
    oper1, eof1 = utils.leer_desde_archivo(movs1, MAX)
    oper2, eof2 = utils.leer_desde_archivo(movs2, MAX)
    total = 0
    while not eof1 or not eof2:
         totcta = 0
         men = min(oper1['cta'], oper2['cta'])
         print 'La menor de las cuentas entre {} y {} es {}'.format(
            oper1['cta'],
            oper2['cta'],
            men
        )
         while oper1['cta'] == men:
                    print 'Procesando la cuenta {} de movs1'.format(oper1['cta'])
                    total += oper1['imp']
                    totcta += oper1['imp']
                    oper1, eof1 = utils.leer_desde_archivo(movs1, MAX)

         while  oper2['cta'] == men:
                    print 'Procesando la cuenta {} de movs2'.format(oper2['cta'])
                    total += oper2['imp']
                    totcta += oper2['imp']
                    oper2, eof2 = utils.leer_desde_archivo(movs2, MAX)

         print 'Total por cta:', men , totcta

print 'Total Gral:', total<jupyter_output>La menor de las cuentas entre 1 y 1 es 1
Procesando la cuenta 1 de movs1
Procesando la cuenta 1 de movs1
Procesando la cuenta 1 de movs2
Total por cta: 1 1850
La menor de las cuentas entre 2 y 2 es 2
Procesando la cuenta 2 de movs1
Procesando la cuenta 2 de movs1
Procesando la cuenta 2 de movs2
Total por cta: 2 2100
La menor de las cuentas entre 10 y 3 es 3
Procesando la cuenta 3 de movs2
Total por cta: 3 700
La menor de las cuentas entre 10 y 10 es 10
Procesando la cuenta 10 de movs1
Procesando la cuenta 10 de movs2
Total por cta: 10 1100
La menor de las cuentas entre 99999999 y 15 es 15
Procesando la cuenta 15 de movs2
Total por cta: 15 3
Total Gral: 5753<jupyter_text>Actualizar archivo con novedadesCuando tiene toda la información en un único archivo (comúnmente llamado archivo *maestro*) y en cierto momento se la quiere actualizar a partir de un segundo archivo llamado *novedades* se genera un tercer archivo con toda la información consolidada. Los archivos maestro y novedades deberán estar ordenados por la misma clave, por lo que el nuevo archivo maestro también debe quedar ordenado.Por ejemplo, si contamos con un archivo llamado *cuentas.pkl* que en casa posición tiene la información correspondiente a una cuenta bancaria:* **nro_cuenta**: Número de cuenta* **tituar**: Titular de la cuenta* **saldo**: Saldo de la cuenta* **tipo_cuenta**: Tipo de cuenta* **moneda**: Moneda en la cual opera la cuentaY uno que tenga las novedades diarias llamado *movimientos.pkl* con la siguiente información:* **tipo**: Tipo de movimiento, es un string de una letra que puede ser A (alta), B (baja), M (modificación)* **nro_cuenta*** Si es:  * *alta*(se asume saldo 0):    * **titular**    * **tipo_cuenta**    * **moneda**  * *modificación*:    * **tipo_movimiento**: Un string que será una de las siguientes opciones: "credito" (cuando ingresa plata a la cuenta) o "debito" (cuando extraen plata de la cuenta)    * **monto**: Monto a acreditar o debitar de la cuenta  * *baja*: no es necesario agregar más campos<jupyter_code>import utils


def crear_archivo_maestro():
    cuentas = [
        {'nro_cuenta': 1, 'saldo': 7094, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_1'}, 
        {'nro_cuenta': 2, 'saldo': 2896, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_2'}, 
        {'nro_cuenta': 3, 'saldo': 14424, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 'titular': 'cliente_3'}, 
        {'nro_cuenta': 5, 'saldo': 7156, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 'titular': 'cliente_5'}, 
        {'nro_cuenta': 8, 'saldo': 7500, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 'titular': 'cliente_8'}, 
        {'nro_cuenta': 9, 'saldo': 2128, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_9'}, 
        {'nro_cuenta': 13, 'saldo': 13524, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 'titular': 'cliente_13'}, 
        {'nro_cuenta': 15, 'saldo': 9479, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_15'}, 
        {'nro_cuenta': 21, 'saldo': 8462, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_21'}, 
        {'nro_cuenta': 25, 'saldo': 6258, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_25'}, 
        {'nro_cuenta': 32, 'saldo': 14082, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_32'}
    ]
    with open('cuentas.pkl', 'wb') as archivo:
        for cuenta in cuentas:
            utils.guardar_en_archivo(archivo, cuenta)


def crear_archivo_novedades():
    novedades = [
        {'nro_cuenta': 1, 'tipo': 'B'},
        {'nro_cuenta': 2, 'monto': 731, 
         'tipo_movimiento': 'debito', 'tipo': 'M'},
        {'nro_cuenta': 3, 'monto': 791, 
         'tipo_movimiento': 'debito', 'tipo': 'M'},
        {'nro_cuenta': 4, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 
         'titular': 'cliente_4', 'tipo': 'A'},
        {'nro_cuenta': 8, 'monto': 750, 
         'tipo_movimiento': 'debito', 'tipo': 'M'},
        {'nro_cuenta': 11, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 'titular': 'cliente_11', 
         'tipo': 'A'},
        {'nro_cuenta': 13, 'monto': 481, 
         'tipo_movimiento': 'debito', 'tipo': 'M'},
        {'nro_cuenta': 15, 'tipo': 'B'},
        {'nro_cuenta': 16, 'moneda': '$', 
         'tipo_cuenta': 'debito', 'titular': 'cliente_16', 
         'tipo': 'A'},
        {'nro_cuenta': 19, 'moneda': '$', 
         'tipo_cuenta': 'corriente', 'titular': 'cliente_19', 
         'tipo': 'A'},
        {'nro_cuenta': 21, 'monto': 653, 
         'tipo_movimiento': 'debito', 'tipo': 'M'},
        {'nro_cuenta': 25, 'tipo': 'B'},
        {'nro_cuenta': 32, 'tipo': 'B'},
    ]
    with open('movimientos.pkl', 'wb') as archivo:
        for nov in novedades:
            utils.guardar_en_archivo(archivo, nov)


################### Apareo ###################

def dar_de_alta(archivo, novedad):
    del novedad['tipo']  # Le borro el campo tipo que no
                         # existe en el archivo maestro
    novedad['saldo'] = 0  # Inicializo el saldo en 0
    utils.guardar_en_archivo(archivo, novedad)

    
def modificar_cuenta(archivo, cuenta, novedad):
    if novedad['tipo_movimiento'] == 'credito':
        monto = novedad['monto']
    else:
        monto = -1 * novedad['monto']

    cuenta['saldo'] += monto
    utils.guardar_en_archivo(archivo, cuenta)
    
def apareo(maestro, novedades, nuevo):
    cuenta, eof_ctas = utils.leer_desde_archivo(maestro)
    nov, eof_novs = utils.leer_desde_archivo(novedades)
    while not eof_ctas and not eof_novs:
        print 'Procesando cuenta nro {} y novedad {} del tipo {}'.format(
            cuenta['nro_cuenta'], nov['nro_cuenta'], nov['tipo']
        )
        if nov['nro_cuenta'] < cuenta['nro_cuenta'] and nov['tipo'] == 'A':
            # Si es un alta, acomodo el registro y lo guardo
            dar_de_alta(nuevo, nov)
            nov, eof_novs = utils.leer_desde_archivo(novedades)

            # No puede ser una B o M porque habría un error
        elif nov['nro_cuenta'] == cuenta['nro_cuenta']:
            if nov['tipo'] == 'M':
                # Si es una modificación, actualizo la cuenta, 
                # guardo y leo de los dos archivos
                modificar_cuenta(nuevo, cuenta, nov)

            cuenta, eof_ctas = utils.leer_desde_archivo(maestro)
            nov, eof_novs = utils.leer_desde_archivo(novedades)

            # Si fuera una B, tendría que ignorarlos y leer de 
            # los archivos igual.

            # No puede ser una A porque habría un error
        elif nov['nro_cuenta'] > cuenta['nro_cuenta']:
            # Si la novedad tiene un número de cuenta mayor, 
            # significa que para esa cuenta no hubo novedades
            # por lo que la guardo tal cual esta sin modificar
            # y leo la siguiente
            utils.guardar_en_archivo(nuevo, cuenta)
            cuenta, eof_ctas = utils.leer_desde_archivo(maestro)

    # Como salí del while, termine con al menos uno de los
    # dos archivos, por lo que ahora puedeo leer lo que
    # quedaba y guardarlo casi tal cual vienen
    while not eof_ctas:
        print 'Procesando cuenta nro {}'.format(cuenta['nro_cuenta'])
        utils.guardar_en_archivo(nuevo, cuenta)
        cuenta, eof_ctas = utils.leer_desde_archivo(maestro)

    while not eof_novs:
        print 'Procesando la novedad {} del tipo {}'.format(
            nov['nro_cuenta'], nov['tipo']
        )
        del nov['tipo']
        nov['saldo'] = 0
        utils.guardar_en_archivo(nuevo, nov)
        nov, eof_novs = utils.leer_desde_archivo(novedades)


def mostrar_archivo_nuevo():
    print 'El archivo nuevo tiene los registros:'
    with open('nuevo.pkl', 'rb') as nuevo:
        cuenta, eof_ctas = utils.leer_desde_archivo(nuevo)
        while not eof_ctas:
            print cuenta
            cuenta, eof_ctas = utils.leer_desde_archivo(nuevo)            


crear_archivo_maestro()
crear_archivo_novedades()

with open('cuentas.pkl', 'rb') as maestro, \
    open('movimientos.pkl', 'rb') as novedades, \
    open('nuevo.pkl', 'wb') as nuevo:
        apareo(maestro, novedades, nuevo)

mostrar_archivo_nuevo()<jupyter_output>Procesando cuenta nro 1 y novedad 1 del tipo B
Procesando cuenta nro 2 y novedad 2 del tipo M
Procesando cuenta nro 3 y novedad 3 del tipo M
Procesando cuenta nro 5 y novedad 4 del tipo A
Procesando cuenta nro 5 y novedad 8 del tipo M
Procesando cuenta nro 8 y novedad 8 del tipo M
Procesando cuenta nro 9 y novedad 11 del tipo A
Procesando cuenta nro 13 y novedad 11 del tipo A
Procesando cuenta nro 13 y novedad 13 del tipo M
Procesando cuenta nro 15 y novedad 15 del tipo B
Procesando cuenta nro 21 y novedad 16 del tipo A
Procesando cuenta nro 21 y novedad 19 del tipo A
Procesando cuenta nro 21 y novedad 21 del tipo M
Procesando cuenta nro 25 y novedad 25 del tipo B
Procesando cuenta nro 32 y novedad 32 del tipo B
El archivo nuevo tiene los registros:
{'nro_cuenta': 2, 'saldo': 2165, 'moneda': '$', 'titular': 'cliente_2', 'tipo_cuenta': 'debito'}
{'nro_cuenta': 3, 'saldo': 13633, 'moneda': '$', 'titular': 'cliente_3', 'tipo_cuenta': 'corriente'}
{'nro_cuenta': 4, 'saldo': 0, 'moneda': '$[...]
{'fraction_non_alphanumeric': 0.11599285598311414, 'fraction_numerical': 0.04387075823997402, 'mean_word_length': 3.456729377713459, 'pattern_counts': {'":': 0, '<': 60, '<?xml version=': 0, '>': 60, 'https://': 1, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 3, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '252177', 'license': 'Apache-2.0', 'n_tokens_mistral': 11965, 'n_tokens_neox': 11007, 'n_words': 3615, 'path': 'Clase 06 - Archivos binarios, Corte de control.ipynb', 'repo_name': 'gsorianob/fiuba', 'size': '30963'}