Puedes pedir las versiones impresa y ebook de Think Python 3e en Bookshop.org y Amazon.
Crédito: las fotos se descargaron de Lorem Picsum, un servicio que proporciona imágenes de marcador de posición. El nombre es una referencia a “lorem ipsum”, que es un nombre para texto de marcador de posición.
# This cell downloads an archive file that contains the the files we'll
# use for the examples in this chapter.
download('https://github.com/AllenDowney/ThinkPython/raw/v3/photos.zip');
# WARNING: This cell removes the photos/ directory if it already exists.
# Any files already in the photos/ directory will be deleted.
# !rm -rf photos/
!unzip -o photos.zip
13. Archivos y bases de datos#
La mayoría de los programas que hemos visto hasta ahora son efímeros en el sentido de que se ejecutan durante poco tiempo y producen output, pero cuando terminan, sus datos desaparecen. Cada vez que ejecutas un programa efímero, empieza desde cero.
Otros programas son persistentes: se ejecutan durante mucho tiempo (o todo el tiempo); mantienen al menos parte de sus datos en almacenamiento a largo plazo; y, si se cierran y se reinician, continúan donde lo dejaron.
Una forma sencilla de que los programas mantengan sus datos es leer y escribir archivos de texto. Una alternativa más versátil es almacenar datos en una base de datos. Las bases de datos son archivos especializados que se pueden leer y escribir de forma más eficiente que los archivos de texto, y proporcionan capacidades adicionales.
En este capítulo escribiremos programas que leen y escriben archivos de texto y bases de datos, y como ejercicio escribirás un programa que busca duplicados en una colección de fotos. Pero antes de poder trabajar con un archivo, tienes que encontrarlo, así que empezaremos con nombres de archivo, rutas y directorios.
13.1. Nombres de archivo y rutas#
Los archivos se organizan en directorios, también llamados “carpetas”. Todo programa en ejecución tiene un directorio de trabajo actual, que es el directorio por defecto para la mayoría de operaciones. Por ejemplo, cuando abres un archivo, Python lo busca en el directorio de trabajo actual.
El módulo os proporciona funciones para trabajar con archivos y directorios (“os” significa “operating system”).
Proporciona una función llamada getcwd que obtiene el nombre del directorio de trabajo actual.
# this cell replaces `os.cwd` with a function that returns a fake path
import os
def getcwd():
return "/home/dinsdale"
os.getcwd = getcwd
import os
os.getcwd()
El resultado en este ejemplo es el directorio personal de un usuario llamado dinsdale.
Una string como '/home/dinsdale' que identifica un archivo o directorio se llama ruta.
Un nombre de archivo sencillo como 'memo.txt' también se considera una ruta, pero es un ruta relativa porque especifica un nombre de archivo relativo al directorio actual.
En este ejemplo, el directorio actual es /home/dinsdale, así que 'memo.txt' equivale a la ruta completa '/home/dinsdale/memo.txt'.
Un ruta que empieza con / no depende del directorio actual – se llama ruta absoluta.
Para encontrar la ruta absoluta de un archivo, puedes usar abspath.
os.path.abspath('memo.txt')
El módulo os proporciona otras funciones para trabajar con nombres de archivo y rutas.
listdir devuelve una lista con el contenido del directorio indicado, incluyendo archivos y otros directorios.
Aquí tienes un ejemplo que lista el contenido de un directorio llamado photos.
os.listdir('photos')
Este directorio contiene un archivo de texto llamado notes.txt y tres directorios.
Los directorios contienen archivos de imagen en formato JPEG.
os.listdir('photos/jan-2023')
Para comprobar si existe un archivo o directorio, podemos usar os.path.exists.
os.path.exists('photos')
os.path.exists('photos/apr-2023')
Para comprobar si una ruta se refiere a un archivo o a un directorio, podemos usar isdir, que devuelve True si una ruta se refiere a un directorio.
os.path.isdir('photos')
Y isfile, que devuelve True si una ruta se refiere a un archivo.
os.path.isfile('photos/notes.txt')
Un desafío al trabajar con rutas es que se ven distintos en diferentes sistemas operativos.
En macOS y sistemas UNIX como Linux, los nombres de directorios y archivos en una ruta se separan con una barra inclinada, /.
Windows usa una barra invertida, \.
Así que, si ejecutas estos ejemplos en Windows, verás barras invertidas en los rutas y tendrás que reemplazar las barras inclinadas de los ejemplos.
O, para escribir código que funcione en ambos sistemas, puedes usar os.path.join, que une nombres de directorio y de archivo en una ruta usando una barra inclinada o invertida, según el sistema operativo que estés usando.
os.path.join('photos', 'jan-2023', 'photo1.jpg')
Más adelante en este capítulo usaremos estas funciones para buscar en un conjunto de directorios y encontrar todos los archivos de imagen.
13.2. f-strings#
Una forma en que los programas almacenan datos es escribirlos en un archivo de texto.
Por ejemplo, supón que observas camellos y quieres registrar el número de camellos que has visto durante un periodo de observación.
Y supón que en un año y medio has visto 23 camellos.
Los datos de tu cuaderno de observación de camellos podrían verse así.
num_years = 1.5
num_camels = 23
Para escribir estos datos en un archivo, puedes usar el método write, que vimos en el Capítulo 8.
El argumento de write tiene que ser una string, así que si queremos poner otros valores en un archivo, tenemos que convertirlos a strings.
La forma más sencilla de hacerlo es con la función integrada str.
Así es como se ve:
writer = open('camel-spotting-book.txt', 'w')
writer.write(str(num_years))
writer.write(str(num_camels))
writer.close()
Eso funciona, pero write no añade un espacio ni una nueva línea a menos que lo incluyas explícitamente.
Si volvemos a leer el archivo, vemos que los dos números quedan pegados.
open('camel-spotting-book.txt').read()
Como mínimo, deberíamos añadir espacios en blanco entre los números. Y ya que estamos, añadamos algo de texto explicativo.
Para escribir una combinación de strings y otros valores, podemos usar una f-string, que es una string que tiene la letra f antes de la comilla de apertura y contiene una o más expresiones de Python entre llaves.
La siguiente f-string contiene una expresión, que es el nombre de una variable.
f'I have spotted {num_camels} camels'
El resultado es una string donde la expresión se ha evaluado y se ha reemplazado por el resultado. Puede haber más de una expresión.
f'In {num_years} years I have spotted {num_camels} camels'
Y las expresiones pueden contener operadores y llamadas a función.
line = f'In {round(num_years * 12)} months I have spotted {num_camels} camels'
line
Así que podríamos escribir los datos en un archivo de texto así.
writer = open('camel-spotting-book.txt', 'w')
writer.write(f'Years of observation: {num_years}\n')
writer.write(f'Camels spotted: {num_camels}\n')
writer.close()
Ambas f-strings terminan con la secuencia \n, que añade un carácter de nueva línea.
Podemos volver a leer el archivo así:
data = open('camel-spotting-book.txt').read()
print(data)
En una f-string, una expresión entre llaves se convierte en una string, así que puedes incluir listas, diccionarios y otros tipos.
t = [1, 2, 3]
d = {'one': 1}
f'Here is a list {t} and a dictionary {d}'
Si una f-string contiene una expresión no válida, el resultado es un error.
f'This is not a valid expression {t + 2}'
13.3. YAML#
Una de las razones por las que los programas leen y escriben archivos es para almacenar datos de configuración, que son información que especifica qué debe hacer el programa y cómo.
Por ejemplo, en un programa que busca fotos duplicadas, podríamos tener un diccionario llamado config que contiene el nombre del directorio donde buscar, el nombre de otro directorio donde debería almacenar los resultados y una lista de extensiones de archivo que debería usar para identificar archivos de imagen.
Así podría verse:
config = {
'photo_dir': 'photos',
'data_dir': 'photo_info',
'extensions': ['jpg', 'jpeg'],
}
Para escribir estos datos en un archivo de texto, podríamos usar f-strings, como en la sección anterior. Pero es más fácil usar un módulo llamado yaml que está diseñado justo para este tipo de cosas.
El módulo yaml proporciona funciones para trabajar con archivos YAML, que son archivos de texto con un formato pensado para que sean fáciles de leer y escribir tanto para humanos como para programas.
Aquí tienes un ejemplo que usa la función dump para escribir el diccionario config en un archivo YAML.
# this cell installs the pyyaml package, which provides the yaml module
try:
import yaml
except ImportError:
!pip install pyyaml
import yaml
config_filename = 'config.yaml'
writer = open(config_filename, 'w')
yaml.dump(config, writer)
writer.close()
Si volvemos a leer el contenido del archivo, podemos ver cómo es el formato YAML.
readback = open(config_filename).read()
print(readback)
Ahora podemos usar safe_load para volver a leer el archivo YAML.
reader = open(config_filename)
config_readback = yaml.safe_load(reader)
config_readback
El resultado es un nuevo diccionario que contiene la misma información que el original, pero no es el mismo diccionario.
config is config_readback
Convertir un objeto como un diccionario en una string se llama serialización. Convertir la string de vuelta en un objeto se llama deserialización. Si serializas y luego deserializas un objeto, el resultado debería ser equivalente al original.
13.4. Shelve#
Hasta ahora hemos estado leyendo y escribiendo archivos de texto – ahora consideremos las bases de datos. Una base de datos es un archivo organizado para almacenar datos. Algunas bases de datos se organizan como una tabla con filas y columnas de información. Otras se organizan como un diccionario que mapea claves a valores; a veces se llaman almacenes clave-valor.
El módulo shelve proporciona funciones para crear y actualizar un almacén clave-valor llamado “shelf”.
Como ejemplo, crearemos un shelf para contener captions de las figuras del directorio photos.
Usaremos el diccionario config para obtener el nombre del directorio donde deberíamos poner el shelf.
config['data_dir']
Podemos usar os.makedirs para crear este directorio, si todavía no existe.
os.makedirs(config['data_dir'], exist_ok=True)
Y os.path.join para crear una ruta que incluya el nombre del directorio y el nombre del archivo del shelf, captions.
db_file = os.path.join(config['data_dir'], 'captions')
db_file
Ahora podemos usar shelve.open para abrir el archivo del shelf.
El argumento c indica que el archivo debería crearse si es necesario.
import shelve
db = shelve.open(db_file, 'c')
db
Si obtienes un error como db type could not be determined, la causa más probable es que ya exista un archivo con el mismo nombre, pero que no sea una base de datos shelve válida (por ejemplo, puede estar corrupto o haber sido creado por otra cosa).
En ese caso, la solución más sencilla es eliminar el archivo existente y volver a ejecutar el código para que shelve.open pueda crear una base de datos nueva.
El valor de retorno es oficialmente un objeto DbfilenameShelf, llamado de manera más informal un objeto shelf.
El objeto shelf se comporta de muchas maneras como un diccionario. Por ejemplo, podemos usar el operador de corchetes para añadir un elemento, que es un mapeo de una clave a un valor.
key = 'jan-2023/photo1.jpg'
db[key] = 'Cat nose'
En este ejemplo, la clave es la ruta a un archivo de imagen y el valor es una string que describe la imagen.
También usamos el operador de corchetes para buscar una clave y obtener el valor correspondiente.
value = db[key]
value
Si haces otra asignación a una clave existente, shelve reemplaza el valor anterior.
db[key] = 'Close up view of a cat nose'
db[key]
Algunos métodos de diccionario, como keys, values e items, también funcionan con objetos shelf.
list(db.keys())
list(db.values())
Podemos usar el operador in para comprobar si una clave aparece en el shelf.
key in db
Y podemos usar una sentencia for para iterar sobre las claves.
for key in db:
print(key, ':', db[key])
Como con otros archivos, deberías cerrar la base de datos cuando termines.
db.close()
Ahora, si listamos el contenido del directorio de datos, vemos dos archivos.
# When you open a shelve file, a backup file is created that has the suffix `.bak`.
# If you run this notebook more than once, you might see that file left behind.
# This cell removes it so the output shown in the book is correct.
!rm -f photo_info/captions.bak
os.listdir(config['data_dir'])
captions.dat contiene los datos que acabamos de almacenar.
captions.dir contiene información sobre la organización de la base de datos que hace que el acceso sea más eficiente.
El sufijo dir significa “directorio”, pero no tiene nada que ver con los directorios con los que hemos estado trabajando y que contienen archivos.
13.5. Almacenar estructuras de datos#
En el ejemplo anterior, las claves y valores del shelf son strings. Pero también podemos usar un shelf para contener estructuras de datos como listas y diccionarios.
Como ejemplo, volvamos al ejemplo de anagramas de un ejercicio en el Capítulo 11.
Recuerda que hicimos un diccionario que mapea una string ordenada de letras a la
lista de palabras que se pueden formar con esas letras.
Por ejemplo, la clave 'opst' mapea a la lista ['opts', 'post', 'pots', 'spot', 'stop', 'tops'].
Usaremos la siguiente función para ordenar las letras de una palabra.
def sort_word(word):
return ''.join(sorted(word))
Y aquí tienes un ejemplo.
word = 'pots'
key = sort_word(word)
key
Ahora abramos un shelf llamado anagram_map.
El argumento 'n' significa que siempre deberíamos crear un shelf nuevo y vacío, aunque ya exista uno.
db = shelve.open('anagram_map', 'n')
Ahora podemos añadir un elemento al shelf así.
db[key] = [word]
db[key]
En este elemento, la clave es una string y el valor es una lista de strings.
Ahora supón que encontramos otra palabra que contiene las mismas letras, como tops
word = 'tops'
key = sort_word(word)
key
La clave es la misma que en el ejemplo anterior, así que queremos añadir una segunda palabra a la misma lista de strings.
Así es como lo haríamos si db fuera un diccionario.
db[key].append(word) # INCORRECT
Pero si lo ejecutamos y luego buscamos la clave en el shelf, parece que no se ha actualizado.
db[key]
Este es el problema: cuando buscamos la clave, obtenemos una lista de strings, pero si modificamos la lista de strings, eso no afecta al shelf. Si queremos actualizar el shelf, tenemos que leer el valor antiguo, actualizarlo y luego escribir el nuevo valor de vuelta en el shelf.
anagram_list = db[key]
anagram_list.append(word)
db[key] = anagram_list
Ahora el valor del shelf está actualizado.
db[key]
Como ejercicio, puedes terminar este ejemplo leyendo la lista de palabras y almacenando todos los anagramas en un shelf.
db.close()
13.6. Comprobar archivos equivalentes#
Ahora volvamos al objetivo de este capítulo: buscar archivos diferentes que contienen los mismos datos. Una forma de comprobarlo es leer el contenido de ambos archivos y compararlo.
Si los archivos contienen imágenes, tenemos que abrirlos con el modo 'rb', donde 'r' significa que queremos leer el contenido y 'b' indica modo binario.
En modo binario, el contenido no se interpreta como texto – se trata como una secuencia de bytes.
Aquí tienes un ejemplo que abre y lee un archivo de imagen.
path1 = 'photos/jan-2023/photo1.jpg'
data1 = open(path1, 'rb').read()
type(data1)
El resultado de read es un objeto bytes – como sugiere el nombre, contiene una secuencia de bytes.
En general, el contenido de un archivo de imagen no es legible para humanos.
Pero si leemos el contenido de un segundo archivo, podemos usar el operador == para comparar.
path2 = 'photos/jan-2023/photo2.jpg'
data2 = open(path2, 'rb').read()
data1 == data2
Estos dos archivos no son equivalentes.
Encapsulemos lo que tenemos hasta ahora en una función.
def same_contents(path1, path2):
data1 = open(path1, 'rb').read()
data2 = open(path2, 'rb').read()
return data1 == data2
Si solo tenemos dos archivos, esta función es una buena opción. Pero supón que tenemos un gran número de archivos y queremos saber si dos cualesquiera contienen los mismos datos. Sería ineficiente comparar cada par de archivos.
Una alternativa es usar una función hash, que toma el contenido de un archivo y calcula un digest, que normalmente es un entero grande. Si dos archivos contienen los mismos datos, tendrán el mismo digest. Si dos archivos son diferentes, casi siempre tendrán digests diferentes.
El módulo hashlib proporciona varias hash funciones – la que usaremos se llama md5.
Empezaremos usando hashlib.md5 para crear un objeto HASH.
import hashlib
md5_hash = hashlib.md5()
type(md5_hash)
El objeto HASH proporciona un método update que toma el contenido del archivo como argumento.
md5_hash.update(data1)
Ahora podemos usar hexdigest para obtener el digest como una string de dígitos hexadecimales que representan un entero en base 16.
digest = md5_hash.hexdigest()
digest
La siguiente función encapsula estos pasos.
def md5_digest(filename):
data = open(filename, 'rb').read()
md5_hash = hashlib.md5()
md5_hash.update(data)
digest = md5_hash.hexdigest()
return digest
Si hacemos hash del contenido de un archivo diferente, podemos confirmar que obtenemos un digest distinto.
filename2 = 'photos/feb-2023/photo2.jpg'
md5_digest(filename2)
Ahora tenemos casi todo lo que necesitamos para encontrar archivos equivalentes. El último paso es buscar en un directorio y encontrar todos los archivos de imagen.
13.7. Recorrer directorios#
La siguiente función toma como argumento el directorio donde queremos buscar.
Usa listdir para iterar sobre el contenido del directorio.
Cuando encuentra un archivo, imprime su ruta completa.
Cuando encuentra un directorio, se llama a sí misma recursivamente para buscar en el subdirectorio.
def walk(dirname):
for name in os.listdir(dirname):
path = os.path.join(dirname, name)
if os.path.isfile(path):
print(path)
elif os.path.isdir(path):
walk(path)
Podemos usarla así:
walk('photos')
El orden de los resultados depende de detalles del sistema operativo.
13.8. Depuración#
Cuando lees y escribes archivos, puedes encontrarte con problemas relacionados con espacios en blanco.
Estos errores pueden ser difíciles de depurar porque los caracteres de espacio en blanco normalmente son invisibles.
Por ejemplo, aquí tienes una string que contiene espacios, un tab representado por la secuencia \t y una nueva línea representada por la secuencia \n.
Cuando la imprimimos, no vemos los caracteres de espacio en blanco.
s = '1 2\t 3\n 4'
print(s)
La función integrada repr puede ayudar. Toma cualquier objeto como argumento y devuelve una representación en string del objeto.
Para strings, representa los caracteres de espacio en blanco con secuencias de barra invertida.
print(repr(s))
Esto puede ser útil para depurar.
Otro problema que puedes encontrar es que distintos sistemas usan distintos caracteres para indicar el final de una línea. Algunos sistemas usan una nueva línea, representada como \n. Otros usan un carácter de retorno, representado como \r.
Algunos usan ambos. Si mueves archivos entre sistemas diferentes, estas
inconsistencias pueden causar problemas.
La capitalización de los nombres de archivo es otro problema que puedes encontrar si trabajas con distintos sistemas operativos. En macOS y UNIX, los nombres de archivo pueden contener letras minúsculas y mayúsculas, dígitos y la mayoría de símbolos. Pero muchas aplicaciones de Windows ignoran la diferencia entre letras minúsculas y mayúsculas, y varios símbolos que se permiten en macOS y UNIX no se permiten en Windows.
13.9. Glosario#
efímero: Un programa efímero normalmente se ejecuta durante poco tiempo y, cuando termina, sus datos se pierden.
persistente: Un programa persistente se ejecuta indefinidamente y mantiene al menos parte de sus datos en almacenamiento permanente.
directorio: Una colección de archivos y otros directorios.
directorio de trabajo actual: El directorio por defecto usado por un programa a menos que se especifique otro directorio.
ruta: Una string que especifica una secuencia de directorios, que a menudo conduce a un archivo.
ruta relativa: Un ruta que empieza desde el directorio de trabajo actual, o desde algún otro directorio especificado.
ruta absoluta: Un ruta que no depende del directorio actual.
f-string:
Una string que tiene la letra f antes de la comilla de apertura y contiene una o más expresiones entre llaves.
datos de configuración: Datos, a menudo almacenados en un archivo, que especifican qué debe hacer un programa y cómo.
serialización: Convertir un objeto en una string.
deserialización: Convertir una string en un objeto.
base de datos: Un archivo cuyo contenido está organizado para realizar ciertas operaciones de manera eficiente.
almacenes clave-valor: Una base de datos cuyo contenido está organizado como un diccionario con claves que corresponden a valores.
modo binario: Una forma de abrir un archivo para que el contenido se interprete como una secuencia de bytes en lugar de como una secuencia de caracteres.
función hash: Una función que toma un objeto y calcula un entero, que a veces se llama digest.
digest: El resultado de una función hash, especialmente cuando se usa para comprobar si dos objetos son iguales.
13.10. Ejercicios#
# This cell tells Jupyter to provide detailed debugging information
# when a runtime error occurs. Run it before working on the exercises.
%xmode Verbose
13.10.1. Pregunta a un asistente virtual#
Hay varios temas que surgieron en este capítulo que no expliqué en detalle. Aquí tienes algunas preguntas que puedes hacerle a un asistente virtual para obtener más información.
“¿Cuáles son las diferencias entre programas efímeros y persistentes?”
“¿Cuáles son algunos ejemplos de programas persistentes?”
“¿Cuál es la diferencia entre una ruta relativa y una ruta absoluta?”
“¿Por qué el módulo
yamltiene funciones llamadasloadysafe_load?”“Cuando escribo un shelf de Python, ¿qué son los archivos con sufijos
datydir?”“Además de los almacenes clave-valor, ¿qué otros tipos de bases de datos existen?”
“Cuando leo un archivo, ¿cuál es la diferencia entre modo binario y modo texto?”
“¿Cuáles son las diferencias entre un objeto bytes y una string?”
“¿Qué es una función hash?”
“¿Qué es un digest MD5?”
Como siempre, si te quedas atascado en alguno de los ejercicios siguientes, considera pedir ayuda a un AV. Junto con tu pregunta, quizá quieras pegar las funciones relevantes de este capítulo.
13.10.2. Ejercicio#
Escribe una función llamada replace_all que tome como argumentos una string patrón, una string de reemplazo y dos nombres de archivo.
Debería leer el primer archivo y escribir el contenido en el segundo archivo (creándolo si es necesario).
Si la string patrón aparece en cualquier parte del contenido, debería reemplazarse por la string de reemplazo.
Aquí tienes un esquema de la función para empezar.
def replace_all(old, new, source_path, dest_path):
# read the contents of the source file
reader = open(source_path)
# replace the old string with the new
# write the result into the destination file
Para probar tu función, lee el archivo photos/notes.txt, reemplaza 'photos' por 'images' y escribe el resultado en el archivo photos/new_notes.txt.
source_path = 'photos/notes.txt'
open(source_path).read()
dest_path = 'photos/new_notes.txt'
old = 'photos'
new = 'images'
replace_all(old, new, source_path, dest_path)
open(dest_path).read()
13.10.3. Ejercicio#
En una sección anterior, usamos el módulo shelve para crear un almacén clave-valor que mapea una string ordenada de letras a una lista de anagramas.
Para terminar el ejemplo, escribe una función llamada add_word que tome como argumentos una string y un objeto shelf.
Debería ordenar las letras de la palabra para crear una clave, y luego comprobar si la clave ya está en el shelf. Si no, debería crear una lista que contenga la nueva palabra y añadirla al shelf. Si sí, debería añadir la nueva palabra al valor existente.
Puedes usar este bucle para probar tu función.
download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/words.txt');
word_list = open('words.txt').read().split()
db = shelve.open('anagram_map', 'n')
for word in word_list:
add_word(word, db)
Si todo funciona, deberías poder buscar una clave como 'opst' y obtener una lista de palabras que se pueden formar con esas letras.
db['opst']
for key, value in db.items():
if len(value) > 8:
print(value)
db.close()
13.10.4. Ejercicio#
En una colección grande de archivos, puede haber más de una copia del mismo archivo, almacenada en distintos directorios o con distintos nombres de archivo.
El objetivo de este ejercicio es buscar duplicados.
Como ejemplo, trabajaremos con archivos de imagen en el directorio photos.
Así es como funcionará:
Usaremos la función
walkdesection_walking_directoriespara buscar en este directorio archivos que terminen con una de las extensiones enconfig['extensions'].Para cada archivo, usaremos
md5_digestdesection_md5_digestpara calcular un digest del contenido.Usando un shelf, haremos un mapeo de cada digest a una lista de rutas con ese digest.
Finalmente, buscaremos en el shelf cualquier digest que mapee a múltiples archivos.
Si encontramos alguno, usaremos
same_contentspara confirmar que los archivos contienen los mismos datos.
Voy a sugerir algunas funciones para escribir primero, y luego lo juntaremos todo.
Para identificar archivos de imagen, escribe una función llamada
is_imageque tome una ruta y una lista de extensiones de archivo, y devuelvaTruesi la ruta termina con una de las extensiones de la lista. Pista: usaos.path.splitext– o pide a un asistente virtual que escriba esta función por ti.
Puedes usar doctest para probar tu función.
from doctest import run_docstring_examples
def run_doctests(func):
run_docstring_examples(func, globals(), name=func.__name__)
run_doctests(is_image)
Escribe una función llamada
add_pathque tome como argumentos una ruta y un shelf. Debería usarmd5_digestpara calcular un digest del contenido del archivo. Luego debería actualizar el shelf, ya sea creando un nuevo elemento que mapee el digest a una lista que contenga la ruta, o añadiendo la ruta a la lista si ya existe.
Escribe una versión de
walkllamadawalk_imagesque tome un directorio y recorra los archivos del directorio y sus subdirectorios. Para cada archivo, debería usaris_imagepara comprobar si es un archivo de imagen yadd_pathpara añadirlo al shelf.
Cuando todo funcione, puedes usar el siguiente programa para crear el shelf, buscar en el directorio photos y añadir rutas al shelf, y luego comprobar si hay múltiples archivos con el mismo digest.
db = shelve.open('photos/digests', 'n')
walk_images('photos')
for digest, paths in db.items():
if len(paths) > 1:
print(paths)
Deberías encontrar un par de archivos que tienen el mismo digest.
Usa same_contents para comprobar si contienen los mismos datos.
Copyright 2024 Allen B. Downey
Código license: MIT License
Text license: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
Traducción al español por midudev (Miguel Ángel Durán).