Sin dolores de cabeza y código ilegible de os.path
Pathlib puede ser mi biblioteca favorita (después de Sklearn, obviamente). Y dado que hay más de 130 mil bibliotecas, eso es decir algo. Pathlib me ayuda a convertir código como este escrito en os.path
:
import osdir_path = "/home/user/documents"
# Find all text files inside a directory
files = [os.path.join(dir_path, f) for f in os.listdir(dir_path)
if os.path.isfile(os.path.join(dir_path, f)) and f.endswith(".txt")]
dentro de esto:
from pathlib import Path# Find all text files inside a directory
files = list(dir_path.glob("*.txt"))
Pathlib salió en Python 3.4 como reemplazo de la pesadilla que era os.path
. También marcó un hito importante para el lenguaje Python en general: finalmente convirtieron cada cosa en un objeto (incluso nada).
El mayor inconveniente de os.path
trataba las rutas del sistema como cadenas, lo que generaba un código desordenado e ilegible y una curva de aprendizaje pronunciada.
Al representar las rutas como completas objetosPathlib resuelve todos estos problemas e introduce elegancia, consistencia y una bocanada de aire fresco en el manejo de caminos.
Y este artículo mío, largamente esperado, describirá algunas de las mejores funciones/características y trucos de pathlib
para realizar tareas que hubieran sido experiencias verdaderamente horribles en os.path
.
Aprender estas funciones de Pathlib hará que todo lo relacionado con rutas y archivos sea más fácil para usted como profesional de datos, especialmente durante los flujos de trabajo de procesamiento de datos en los que tiene que mover miles de imágenes, CSV o archivos de audio.
¡Empecemos!
Trabajando con caminos
1. Creando caminos
Casi todas las características de pathlib
es accesible a través de su Path
class, que puede usar para crear rutas a archivos y directorios.
Hay algunas maneras de crear caminos con Path
. Primero, hay métodos de clase como cwd
y home
para los directorios de usuarios domésticos y de trabajo actuales:
from pathlib import PathPath.cwd()
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib')
Path.home()
PosixPath('/home/bexgboost')
También puede crear rutas a partir de rutas de cadena:
p = Path("documents")p
PosixPath('documents')
Unir rutas es muy fácil en Pathlib con el operador de barra inclinada:
data_dir = Path(".") / "data"
csv_file = data_dir / "file.csv"print(data_dir)
print(csv_file)
data
data/file.csv
Por favor, no dejes que nadie te atrape usando os.path.join
después de este.
Para verificar si una ruta, puede usar la función booleana exists
:
data_dir.exists()
True
csv_file.exists()
True
A veces, el objeto Path completo no será visible y deberá verificar si se trata de un directorio o un archivo. Entonces, puedes usar is_dir
o is_file
funciones para hacerlo:
data_dir.is_dir()
True
csv_file.is_file()
True
La mayoría de las rutas con las que trabaje serán relativas a su directorio actual. Sin embargo, hay casos en los que debe proporcionar la ubicación exacta de un archivo o un directorio para que sea accesible desde cualquier secuencia de comandos de Python. Esto es cuando usas absolute
caminos:
csv_file.absolute()
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib/data/file.csv')
Por último, si tiene la desgracia de trabajar con bibliotecas que aún requieren rutas de cadenas, puede llamar str(path)
:
str(Path.home())
'/home/bexgboost'
La mayoría de las bibliotecas en la pila de datos han soportado durante mucho tiempo
Path
objetos, incluyendosklearn
,pandas
,matplotlib
,seaborn
etc.
2. Atributos de ruta
Path
los objetos tienen muchos atributos útiles. Veamos algunos ejemplos usando este objeto de ruta que apunta a un archivo de imagen.
image_file = Path("images/midjourney.png").absolute()image_file
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib/images/midjourney.png')
Comencemos con el parent
. Devuelve un objeto de ruta que está un nivel por encima del directorio de trabajo actual.
image_file.parent
PosixPath('/home/bexgboost/articles/2023/4_april/1_pathlib/images')
A veces, es posible que solo desee el archivo name
en lugar de todo el camino. Hay un atributo para eso:
image_file.name
'midjourney.png'
que devuelve solo el nombre del archivo con la extensión.
También hay stem
para el nombre del archivo sin el sufijo:
image_file.stem
'midjourney'
O el suffix
mismo con el punto para la extensión del archivo:
image_file.suffix
'.png'
Si desea dividir una ruta en sus componentes, puede utilizar parts
en lugar de str.split('/')
:
image_file.parts
('/',
'home',
'bexgboost',
'articles',
'2023',
'4_april',
'1_pathlib',
'images',
'midjourney.png')
Si desea que esos componentes sean Path
objetos en sí mismos, puede utilizar parents
atributo, que crea un generador:
for i in image_file.parents:
print(i)
/home/bexgboost/articles/2023/4_april/1_pathlib/images
/home/bexgboost/articles/2023/4_april/1_pathlib
/home/bexgboost/articles/2023/4_april
/home/bexgboost/articles/2023
/home/bexgboost/articles
/home/bexgboost
/home
/
Trabajar con archivos
Para crear archivos y escribir en ellos, no tiene que usar open
función más. Solo crea un Path
objeto y write_text
o write_btyes
a ellos:
markdown = data_dir / "file.md"# Create (override) and write text
markdown.write_text("# This is a test markdown")
O, si ya tiene un archivo, puede read_text
o read_bytes
:
markdown.read_text()
'# This is a test markdown'
len(image_file.read_bytes())
1962148
Sin embargo, tenga en cuenta que write_text
o write_bytes
anula el contenido existente de un archivo.
# Write new text to existing file
markdown.write_text("## This is a new line")
# The file is overridden
markdown.read_text()
'## This is a new line'
Para agregar nueva información a archivos existentes, debe usar open
método de Path
objetos en a
(añadir) modo:
# Append text
with markdown.open(mode="a") as file:
file.write("n### This is the second line")markdown.read_text()
'## This is a new linen### This is the second line'
También es común cambiar el nombre de los archivos. rename
El método acepta la ruta de destino para el archivo renombrado.
Para crear la ruta de destino en el directorio actual, es decir, cambiar el nombre del archivo, puede utilizar with_stem
en el camino existente, que sustituye al stem
del archivo original:
renamed_md = markdown.with_stem("new_markdown")markdown.rename(renamed_md)
PosixPath('data/new_markdown.md')
Arriba, file.md
se convierte en new_markdown.md
.
Veamos el tamaño del archivo a través de stat().st_size
:
# Display file size
renamed_md.stat().st_size
49 # in bytes
o la última vez que se modificó el archivo, que fue hace unos segundos:
from datetime import datetimemodified_timestamp = renamed_md.stat().st_mtime
datetime.fromtimestamp(modified_timestamp)
datetime.datetime(2023, 4, 3, 13, 32, 45, 542693)
st_mtime
devuelve una marca de tiempo, que es el conteo de segundos desde el 1 de enero de 1970. Para que sea legible, puede usar el fromtimestamp
funcion de datatime
.
Para eliminar archivos no deseados, puede unlink
a ellos:
renamed_md.unlink(missing_ok=True)
Configuración missing_ok
a True
no generará ninguna alarma si el archivo no existe.
Trabajando con directorios
Hay algunos buenos trucos para trabajar con directorios en Pathlib. Primero, veamos cómo crear directorios recursivamente.
new_dir = (
Path.cwd()
/ "new_dir"
/ "child_dir"
/ "grandchild_dir"
)new_dir.exists()
False
El new_dir
no existe, así que vamos a crearlo con todos sus hijos:
new_dir.mkdir(parents=True, exist_ok=True)
Por defecto, mkdir
crea el último hijo de la ruta dada. Si los padres intermedios no existen, debe configurar parents
a True
.
Para eliminar directorios vacíos, puede usar rmdir
. Si el objeto de ruta dado está anidado, solo se elimina el último directorio secundario:
# Removes the last child directory
new_dir.rmdir()
Para enumerar el contenido de un directorio como ls
en la terminal, puede usar iterdir
. Una vez más, el resultado será un objeto generador, que arrojará el contenido del directorio como objetos de ruta separados, uno a la vez:
for p in Path.home().iterdir():
print(p)
/home/bexgboost/.python_history
/home/bexgboost/word_counter.py
/home/bexgboost/.azure
/home/bexgboost/.npm
/home/bexgboost/.nv
/home/bexgboost/.julia
...
Para capturar todos los archivos con una extensión específica o un patrón de nombre, puede usar el glob
función con una expresión regular.
Por ejemplo, a continuación, encontraremos todos los archivos de texto dentro de mi directorio de inicio con glob("*.txt")
:
home = Path.home()
text_files = list(home.glob("*.txt"))len(text_files)
3 # Only three
Para buscar archivos de texto de forma recursiva, es decir, también dentro de todos los directorios secundarios, puede utilizar globo recursivo con rglob
:
all_text_files = [p for p in home.rglob("*.txt")]len(all_text_files)
5116 # Now much more
Obtenga más información sobre las expresiones regulares aquí.
También puedes usar rglob('*')
para enumerar los contenidos del directorio de forma recursiva. Es como la versión sobrealimentada de iterdir()
.
Uno de los casos de uso de esto es contar la cantidad de formatos de archivo que aparecen dentro de un directorio.
Para ello, importamos el Counter
clase de collections
y proporcione todos los sufijos de archivo dentro de la carpeta de artículos de home
:
from collections import Counterfile_counts = Counter(
path.suffix for path in (home / "articles").rglob("*")
)
file_counts
Counter({'.py': 12,
'': 1293,
'.md': 1,
'.txt': 7,
'.ipynb': 222,
'.png': 90,
'.mp4': 39})
Diferencias del sistema operativo
Lo siento, pero tenemos que hablar sobre esta pesadilla de un problema.
Hasta ahora, hemos estado tratando con PosixPath
objetos, que son los predeterminados para sistemas tipo UNIX:
type(Path.home())
pathlib.PosixPath
Si estuviera en Windows, obtendría un WindowsPath
objeto:
from pathlib import WindowsPath# User raw strings that start with r to write windows paths
path = WindowsPath(r"C:users")
path
NotImplementedError: cannot instantiate 'WindowsPath' on your system
Crear una instancia de la ruta de otro sistema genera un error como el anterior.
Pero, ¿qué sucede si se ve obligado a trabajar con rutas de otro sistema, como el código escrito por compañeros de trabajo que usan Windows?
Como solución, pathlib
ofrece objetos de ruta pura como PureWindowsPath
o PurePosixPath
:
from pathlib import PurePosixPath, PureWindowsPathpath = PureWindowsPath(r"C:users")
path
PureWindowsPath('C:/users')
Estos son objetos de ruta primitivos. Tiene acceso a algunos métodos y atributos de ruta, pero esencialmente, el objeto de ruta sigue siendo una cadena:
path / "bexgboost"
PureWindowsPath('C:/users/bexgboost')
path.parent
PureWindowsPath('C:/')
path.stem
'users'
path.rename(r"C:losers") # Unsupported
AttributeError: 'PureWindowsPath' object has no attribute 'rename'
Conclusión
Si te has dado cuenta, mentí en el título del artículo. En lugar de 15, creo que el recuento de nuevos trucos y funciones fue de 30.
No quería asustarte.
Pero espero haberte convencido lo suficiente como para deshacerte os.path
y empezar a usar pathlib
para operaciones de ruta mucho más fáciles y legibles.
Forjar un nuevo caminoSi tu quieres 🙂
Si disfrutaste este artículo y, seamos sinceros, su estilo de escritura extraño, considera apoyarme registrándote para convertirte en miembro de Medium. La membresía cuesta 4,99 $ al mes y te da acceso ilimitado a todas mis historias y cientos de miles de artículos escritos por gente más experimentada. Si te registras a través de este enlace, ganaré una pequeña comisión sin costo extra para tu bolsillo.
[post_relacionado id=»1731″]