====== Conceptos básicos de archivos y directorios ======
===== Definición de direcciones de archivo o directorio =====
Un archivo tiene habitualmente dos //key properties//: un nombre de archivo o //filename//, y un directorio o //path// donde se encuentra.\\
La estructura de archivos y directorios es diferente en distintos Sistemas Operativos, debemos conocer cómo se organiza en el que estamos trabajando.\\
Además, el símbolo para separar directorios y subdirectorios, también cambia entre Windows (\) y Linux o Mac (/).\\
Para que nuestro programa funcione en cualquier Sistema Operativo, podemos usar la función **Path()** de la biblioteca **pathlib**.
**Path()** devuelve un objeto de tipo Path, que al pasarlo a string, devolverá la cadena en función del Sistema Operativo en el que se esté ejecutando el programa. Al definir direcciones en el programa (usando Path), se usa / por convención, ya que entre los desarrolladores, suele ser linux el Sistema Operativo preferente.\\
from pathlib import Path
myFiles = ['accounts.txt', 'details.csv', 'invite.docx']
for filename in myFiles:
print(Path(r'C:/Users/Al', filename)) # Con Path() se pueden concatenar cadenas que definan el directorio.
# C:\Users\Al\accounts.txt
# C:\Users\Al\details.csv
# C:\Users\Al\invite.docx
La biblioteca pathlib fue introducida en Python 3.4, reemplazando al viejo os.path de versiones anteriores.
==== Concatenar directorios con / ====
Del mismo modo que se pueden concatenar strings con el símbolo +, se pueden concatenar //paths// con el símbolo /.
from pathlib import Path
homeFolder = Path('C:/Users/Al')
subFolder = Path('spam')
homeFolder / subFolder # WindowsPath('C:/Users/Al/spam') # Objeto tipo Path
str(homeFolder / subFolder) # 'C:\\Users\\Al\\spam' # Hay dobles backlashes, ya que estamos en Windows y el primer \ es el carácter de escape
Sólo hay que tener en cuenta al unir //paths// con /, es que uno de los dos primeros valores que se pasan deben ser de tipo Path. Si son strings, por ejemplo, se produce un error TypeError.\\
El operador / reemplaza al viejo os.path.join() de versiones de Python anteriores.
==== Direcciones absolutas y relativas ====
Hay dos formas de especificar un directorio:
* **Dirección absoluta**: Comienza en el directorio raíz.
* **Dirección relativa**: Comienza en el directorio de trabajo.
Además se puede indicar:
* **./** Se indica "Este directorio" o directorio "en el que nos encontramos", el directorio de trabajo.
* **../** Se indica el directorio "padre"
==== Directorio de trabajo y directorio de usuario ====
El directorio de trabajo es el directorio desde el que estamos trabajando, también denominado //Current Working Directory// o //cwd//.
* Comprobar diectorio de trabajo: **Path.cwd()**.
* Cambiar el directorio de trabajo: **os.chdir()**.
El directorio de usuario depende del Sistema Operativo, y es donde se guarda la información del usuario. Se recomienda usar este directorio para poner nuestros programas, ya que así nos aseguramos de tener todos los permisos necesarios.
===== Creación y manejo de directorios =====
==== Creación de directorios ====
Se pueden crear directorios, así como los directorios intermedios definifos si no existen, con la función **os.makedirs()**. Se pasa como argumento un string con la dirección completa.\\
También pueden crearse directorios con el método de Path, **mkdir()**:
from pathlib import Path
Path(r'C:\Users\Al\spam').mkdir() # mkdir() no crea directorios intermedios, para eso usar os.makedirs()
==== Trabajar con direcciones absolutas y relativas ====
El módulo (biblioteca) pathlib proporciona los siguientes métodos para comprobar si un //path// es absoluto o relativo:
* **is_absolute()** True si es absoluto.
* **Path.cwd() / Path('my/relative/path')** Creación de una dirección absoluta a partir de una relativa.
* **Path.home() / Path('my/relative/path')** Creación decuna dirección absoluta a partir de una relativa, tomando como base el diectorio de trabajo.
Otras funciones útiles:
* **os.path.abspath(path)** Retornará un string con el string absoluto de los argumentos.
* **os.path.isabs(path)** True si el argumentos es absoluto.
* **os.path.relpath(path, start)** Retornará una dirección relativa desde start hasta path. Si start no está indicada, tomará en su lugar el directorio de trabajo.
==== Partes de una dirección de archivo ====
Un objeto tipo Path se compone de diferentes partes, que constituyen sus atributos:\\
c:\Users\Al\spam.txt
* C: **Drive** Letra que denota la partición o dispositivo físico.
* C:\ **//Anchor//** Directorio raíz.
* \Users\Al\ **//Parent//** Directorio que contiene el archivo.
* spam.txt **//Name//** Archivo, formado a su vez:
* spam **//Stem//** Tallo, nombre del archivo sin la extensión.
* .txt **//Suffix//** Extensión, define el tipo de archivo.
Para obtener cada atributo, es tan fácil como indicarlo:
p = Path('C:/Users/Al/spam.txt')
p.anchor # 'C:\\'
p.parent # WindowsPath('C:/Users/Al') # parent es el único que es de tipo Path en lugar de string
p.name # 'spam.txt'
p.stem # 'spam'
p.suffix # '.txt'
p.drive # 'C:'
También se puede utilizar el módulo viejo **os**:
* **os.path.dirname(path)** Devuelve un string con todo lo que hay antes del último \ en el argumento.
* **os.path.basename(path)** Devuelve un string con todo lo que hay después del último \ en el argumento.
* **os.path.split(path)** Devuelve una tupla con dos elementos, correspondiente al dirname y al basename.
* **os.sep** Si se pasa como argumento del método split os.sep, devuelve cada elemento de la dirección como strings dentro de una lista:
calcFilePath = 'C:\\Windows\\System32\\calc.exe'
calcFilePath.split(os.sep)
# ['C:', 'Windows', 'System32', 'calc.exe']
==== Buscando tamaños de archivo y contenido de directorios ====
Se pueden buscar tamaños de archivo y contenido de directorios usando el módulo **os**:
* Obtener el tamaño en bytes de un archivo: **os.path.getsize(path)**
* Obtener una lista de los nombres de archivo de un directorio: **os.listdir(path)** Se puede usar en un bucle for en lugar de range() para recorrer los archivos de un directorio.
También se pueden usar patrones //Glob// para obtener archivos de un directorio. Los patrones //Glob// pueden realizar búsquedas de forma similar a la de las expresiones regulares, aunque es más sencillo:
p = Path('C:/Users/Al/Desktop')
p.glob('*') # Devuelve un objeto generador
list(p.glob('*')) # Crea una lista de los archivos que se encuentran en el directorio definido en p, en formato absoluto
como argumentos de glob() se especifica el patrón de forma similar a una expresión regular:
* Será tomado en consideración cualqueir texto que se indique.
* '*' Uno o varios caracteres, cualesquiera. Ejemplo: glob('*.txt') buscaría todos los archivos de texto.
* '?' Unúnico carácter cualquiera.
* Se pueden mezclar para obtener patrones más complejos.
==== Validación de paths ====
Muchas funciones dan un error si la dirección indicada no funciona. Algunos métodos útiles de tipos de objeto Path, son (asumiendo p como objeto tipo Path):
* **p.exists()** True si la dirección existe.
* **p.is_file()** True si la dirección existe y es un archivo.
* **p.is_dir()** True si la dirección existe y es un directorio.
Para estos mismos cometidos podrían usarse los viejos métodos **os.path.exists(path)**, **os.path.isfile(path)**, y **os.path.isdir(path)**.
===== Proceso de lectura/escritura de archivos =====
En este apartado se verá la forma de leer y escribir en archivos de texto plano.
==== Lectura/escritura básica ====
Los métodos de la clase pathlib más sencillos y directos para leer/escribir con archivos son path**.read_text()** y path**.write_text(**string**)**.
* path**.read_text()** devuelve un string con el contenido completo del archivo de texto.
* path**.write_text(**string**)** devuelve el número de caracteres escritos, crea un nuevo archivo, si no existe, y si existe, lo sobreescribe, ojo con esto.
==== Lectura/escritura con open() ====
Proceso a seguir:
- Se llama a la función **open()**, que devuelve un objeto de tipo File: archivo = **open(**dirección del archivo**)**
- Se llama a los métodos **read()** o **write()** del objeto File.
- Se cierra el archivo con el método **close()** del objeto File.
=== Lectura===
La función open() permite un segundo argumento en el que se indica el modo en el que se abre el archivo, por defecto en modo lectura (argumento: 'r').
El método **read()** devuelve en un único string el contenido de todo el archivo.
Con el método **readlines()** se obtiene una lista en la que cada elemento es el string correspondiente a cada línea del archivo (cada string termina con \n, salvo el último, lógicamente).
=== Escritura ===
Para escribir texto en un archivo de texto plano, éste debe de abrirse con open() indicando el modo:
* 'w': Si no existe en archivo, se crea. Comienza a escribir desde el inicio del documento y si hay algo ya escrito, se sobreescribe.
* 'a': Si no existe en archivo, se crea. Comienza a escribir desde el final del documento.
En ambos casos, siempre se escribe la cadena que se pasa como argumento del método **write()**.
Devuelve el número de caracteres escritos.
=== Sentencia with ===
Con el fin de tener un código más limpio, es común a la hora de trabajar con directorios usar la sentencia **with**, que nos permite evitar tener que estar cerrando el archivo con 'close()'. \\
Un ejemplo:
with open('file_path', 'w') as file:
file.write('hello world !')
===== Guardar variables =====
==== Guardar variables en archivos binarios ====
Con el módulo **shelve** se pueden guardar variables en un archivo binario, para después recuperar sus valores. De ese modo, no se pierde informaión al cerrar el programa (las variables normales son destruidas).\\
La forma de usar esta funcionalidad es similar a la de leer/escribir en un archivo:
- Importamos la biblioteca: import shelve
- Damos un nombre a los archivos binarios que se crearán ('mydata'), los abrimos (con shelve se permite tanto leer como escribir) y asignamos el valor devuelto a una variable: shelfFile = shelve.open('mydata')
- Trabajamos con la información que contiene:
- Para escribir (guardar) información, se guarda como se haría en un diccionario.
- Para leer (recuperar) información, también se hace como en un diccionario: atendiendo a la clave entre corchetes.
- Es posible listar la información con la función list()
- Cerramos el binario con close(): ShelfFile.close()
import shelve
shelfFile = shelve.open('mydata')
cats = ['Zophie', 'Pooka', 'Simon']
shelfFile['cats'] = cats
shelfFile['cats'] # ['Zophie', 'Pooka', 'Simon']
list(shelfFile.keys()) # ['cats']
list(shelfFile.values()) # [['Zophie', 'Pooka', 'Simon']]
shelfFile.close()
==== Guardar variables en archivos de texto .py ====
En este caso la idea es guardar las variables en un archivo de python (.py), de forma que pueda importarse como una biblioteca, y de ese modo recuperar los dartos.\\
Para ello se utiliza el método **pformat()** de la biblioteca **pprint** (//Pretty Print//), que devuelve una variable tal y cmo se escribe en código.
import pprint
cats = [{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}]
pprint.pformat(cats) # "[{'desc': 'chubby', 'name': 'Zophie'}, {'desc': 'fluffy', 'name': 'Pooka'}]"
fileObj = open('myCats.py', 'w')
fileObj.write('cats = ' + pprint.pformat(cats) + '\n') # 83
fileObj.close()
import myCats
myCats.cats # [{'name': 'Zophie', 'desc': 'chubby'}, {'name': 'Pooka', 'desc': 'fluffy'}]
myCats.cats[0] # {'name': 'Zophie', 'desc': 'chubby'}
myCats.cats[0]['name'] # 'Zophie'
Con esta funcionalidad los datos se guardan en texto plano, fácil de editar por cualquiera.\\
De este modo se pueden guardar variables de tipos básicos (string, enteros, listas, diccionarios, etc), pero no de tipos más complejos (como Files).\\
Para guardar datos más complejos es necesario usar el módulo shelve.
====== Trabajar con archivos y directorios ======
===== Acciones sobre archivos y directorios =====
Para realizar acciones sobre archivos y directorios, como mover, copiar, renombrar o borrar, vamos a utilizar la biblioteca **shutil** //shell utilities//.
==== Copiar ====
Para copiar un único archivo se utiliza el método **shutil.copy(source, destination)**.
* **source**: Dirección del archivo, incluído, puede ser de tipo Path o string.
* **destination**: Dirección donde se copiará archivo, también puede ser de tipo Path o string. Si se indica un nombre de archivo, además será renombrado con éste.
* retorna un dato Path con el dirección del archivo copiado, incuído éste.
Para copiar un directorio completo, con todos sus subdirectorios y archivos, se utiliza del mismo modo la función **shutil.copytree(source, destination)**. Devuelve un dato Path con el dirección del directorio copiado.
==== Mover y renombrar ====
Para mover un archivo o un directorio, se utiliza el método **shutil.move(source, destination)**:
* **source**: Dirección del archivo, incluído, puede ser de tipo Path o string.
* **destination**: Dirección donde se moverá el archivo, también puede ser de tipo Path o string. Si se indica un nombre de archivo, además será renombrado con éste. OJO, si el último directorio especificado no existe, y no se renombra el archivo, éste será renombrado con el nombre del directorio inexistente ¡no diferencia entre nombre de archivo y nombre de directorio!
* Si algún valor de los directorios intermedios donde se desea transferir el archivo o directorio no existe, se producirá un error FileNotFound.
* Devuelve la dirección del directorio o archivo movido.
==== Borrado de directorios y archivos ====
=== Borrado permanente ===
* Para borrar permanentemente un único archivo: **os.ulink(//path//)**
* Para borrar permanentemente un directorio completamene vacío: **os.rmdir(//path//)**
* Para borrar permanentemente un directorio y todo su contenido: **shutil.rmtree(//path//)**
import os
from pathlib import Path
for filename in Path.home().glob('*.rxt'):
#os.unlink(filename) # Para ver los próximos archivos a eliminar, se comenta esta línea. Si estamos seguros, entonces se descomenta.
print(filename)
=== Borrado "seguro" ===
La idea es enviar los archivos a la papelera de reciclaje, en lugar de borrarlos directamente.
Para ellos usamos el método **send2trash()** del módulo **send2trash**.\\
Es un módulo de terceros que debe instalarse con //pip install --user send2trash//.
>>> import send2trash
>>> baconFile = open('bacon.txt', 'a') # creates the file
>>> baconFile.write('Bacon is not a vegetable.')
25
>>> baconFile.close()
>>> send2trash.send2trash('bacon.txt')
===== Recorrer un árbol de directorios =====
La biblioteca **os** nos proporciona la función **os.walk(//string//)**, que usada en conjunto con un bucle for (en lugar de range()), en cada iteración se mueve por los directorios del árbol, lo que permite recorrer el arbol de directorios:\\
En cada iteración del bucle for, la función **walk()** devuelve:
* String con el directorio actual.
* Una lista de strings con sus subdirectorios.
* Una lista de strings con sus archivos.
'''
C:/
delicius
cats
catnames.txt
zophie.jpg
walnut
waffles
butter.txt
spam.txt
'''
import os
for folderName, subfolders, filenames in os.walk('C:\\delicious'):
print('The current folder is ' + folderName)
for subfolder in subfolders:
print('SUBFOLDER OF ' + folderName + ': ' + subfolder)
for filename in filenames:
print('FILE INSIDE ' + folderName + ': '+ filename)
print('')
The current folder is C:\delicious
SUBFOLDER OF C:\delicious: cats
SUBFOLDER OF C:\delicious: walnut
FILE INSIDE C:\delicious: spam.txt
The current folder is C:\delicious\cats
FILE INSIDE C:\delicious\cats: catnames.txt
FILE INSIDE C:\delicious\cats: zophie.jpg
The current folder is C:\delicious\walnut
SUBFOLDER OF C:\delicious\walnut: waffles
The current folder is C:\delicious\walnut\waffles
FILE INSIDE C:\delicious\walnut\waffles: butter.txt
===== Trabajar con archivos comprimidos ZIP =====
Para trabajar con archivos comprimidos en ZIP, (extensión .zip) usaremo el módulo **zipfile**.\\
La forma de trabajar es muy similar, asignando a tipos de datos ZipFile (similares a Path) el archivo comprimido con el que trabajaremos para gestionar sus datos.
==== Leer ZIP ====
Para leer archivos comprimidos en ZIP:
- Asignamos el archivo a una variable ZipFile con el método ZipFile del módulo zipfile.exampleZip = zipfile.ZipFile(Path.wcd()/'ham'/'meat.zip')
- Podemos listar el contenido con el método **namelist()**, que devuelve una lista de strings con las direcciones relativas del contenido. exampleZip.namelist()
- Podemos obtener información de cualquier achivo comprimido en ZIP con el método **getinfo(//nombre_archivo//)**. Devuelve un objeto en el que se pueden consultar los atributos **file_size** y **compress_size**. spamInfo = exampleZip.getinfo('spam.txt')
spamInfo.file_size
spamInfo.compress_size
- Una vez hemos terminado de leer en el interior del archivo comprimido, lo cerramos con el métoo close(). exampleZip.close()
==== Extraer ZIP ====
Para extraer todo el contenido de un ZIP, se utiliza el método **extractall()**, al que se le puede pasar un argumento string en el que se define la dirección en la que se desea extraer. Si no, se extraerá en el directorio raíz.
import zipfile, os
from pathlib import Path
p = Path.home()
exampleZip = zipfile.ZipFile(p / 'example.zip')
exampleZip.extractall() # exampleZip.extractall('C:\\delicious') # Para extraerlo en una diección concreta.
exampleZip.close()
Para extraer un archivo concreto se usa el método **extract()**, en la que el primer argumento es el archivo a extraer, y el segundo (opcional), la dirección en la que se extraerá (si no existe, se crea).
>>>exampleZip.extract('spam.txt')
'C:\\spam.txt'
>>> exampleZip.extract('spam.txt', 'C:\\some\\new\\folders')
'C:\\some\\new\\folders\\spam.txt'
>>> exampleZip.close()
==== Crear y añadir a ZIP ====
Para crear un nuevo archivo ZIP, se utiliza el método **ZipFile()**, al iguar que se hacía para leer de su interior:
* Su primer argumento será el nombre de archivo.
* El segundo argumento será
* 'w' para añadir archivos o directorios, sobreescribiendo los que había antes.
* 'a' para añadir archivos o directorios sin sobreescribir los que había antes.
Para añadir elementos (archivos y directorios) al ZIP, se usará el método **write()**:
* El primer argumento será un string del archivo o directorio a comprimir.
* El segundo argumento será el algoritmo de comprensión que se usará, por ejemplo ZIP_DEFLATED, que va bien con cualquier tipo de dato).
>>> import zipfile
>>> newZip = zipfile.ZipFile('new.zip', 'w')
>>> newZip.write('spam.txt', compress_type=zipfile.ZIP_DEFLATED)
>>> newZip.close()