pathlib — Rutas del sistema de archivos como objetos

Propósito:Analiza, construye, prueba y trabaja de otra manera con nombres de archivo y rutas utilizando una interfaz orientada a objetos en lugar de operaciones de cadenas de bajo nivel.

Representaciones de ruta

pathlib incluye clases para administrar rutas del sistema de archivos formateadas utilizando la sintaxis estándar de POSIX o de Microsoft Windows. Incluye las llamadas clases «puras», que operan en cadenas pero no interactúan con un sistema de archivos, y clases «concretas», que extienden la interfaz para incluir operaciones que reflejan o modifican datos en el sistema local de archivos.

Las clases puras PurePosixPath y PureWindowsPath puede ser instanciadas y utilizadas en cualquier sistema operativo, ya que solo trabajan en nombres. Para instanciar la clase correcta para trabajar con un sistema real de archivos, usa Path para obtener un PosixPath o WindowsPath, según la plataforma.

Constuyendo rutas

Para instanciar una nueva ruta, dele una cadena como primer argumento. La representación de cadena del objeto de ruta es este valor de nombre. Para crear una nueva ruta que hace referencia a un valor relativo a una ruta existente, usa el operador / para extender la ruta. El argumento para el operador puede ser una cadena u otro objeto de ruta.

pathlib_operator.py
import pathlib

usr = pathlib.PurePosixPath('/usr')
print(usr)

usr_local = usr / 'local'
print(usr_local)

usr_share = usr / pathlib.PurePosixPath('share')
print(usr_share)

root = usr / '..'
print(root)

etc = root / '/etc/'
print(etc)

A medida que se muestra el valor de root en el resultado del ejemplo, el operador combina los valores de ruta como se les da, y no normaliza el resultado cuando contiene la referencia del directorio principal "..". Sin embargo, si un segmento comienza con el separador de ruta, es interpretado como una nueva referencia «raíz» de la misma manera que os.path.join(). Separadores de ruta extra se eliminan del medio del valor de la ruta, como en el ejemplo de etc aquí.

$ python3 pathlib_operator.py

/usr
/usr/local
/usr/share
/usr/..
/etc

Las clases concretas de ruta incluyen un método resolve() para normalizar una ruta buscando en el sistema de archivos directorios y enlaces simbólicos y que producen la ruta absoluta a la que hace referencia un nombre.

pathlib_resolve.py
import pathlib

usr_local = pathlib.Path('/usr/local')
share = usr_local / '..' / 'share'
print(share.resolve())

Aquí la ruta relativa se convierte a la ruta absoluta a /usr/share`. Si la ruta de entrada incluye enlaces simbólicos, esos son ampliados también para permitir que la ruta resuelta se refiera directamente al objetivo.

$ python3 pathlib_resolve.py

/usr/share

Para construir rutas cuando los segmentos no se conocen de antemano, usa joinpath(), pasando cada segmento de ruta como un argumento separado.

pathlib_joinpath.py
import pathlib

root = pathlib.PurePosixPath('/')
subdirs = ['usr', 'local']
usr_local = root.joinpath(*subdirs)
print(usr_local)

Al igual que con el operador /, al llamar joinpath() crea una nueva instancia.

$ python3 pathlib_joinpath.py

/usr/local

Dado un objeto de ruta existente, es fácil construir uno nuevo con diferencias menores como hacer referencia a un archivo diferente en el mismo directorio. Usa with_name() para crear una nueva ruta que reemplace la parte del nombre de una ruta con un nombre de archivo diferente. Utiliza with_suffix() para crear una nueva ruta que reemplace el la extensión del nombre de archivo con un valor diferente.

pathlib_from_existing.py
import pathlib

ind = pathlib.PurePosixPath('source/pathlib/index.rst')
print(ind)

py = ind.with_name('pathlib_from_existing.py')
print(py)

pyc = py.with_suffix('.pyc')
print(pyc)

Ambos métodos devuelven nuevos objetos y no se modifica el original.

$ python3 pathlib_from_existing.py

source/pathlib/index.rst
source/pathlib/pathlib_from_existing.py
source/pathlib/pathlib_from_existing.pyc

Analizando rutas

Los objetos de ruta tienen métodos y propiedades para extraer valores parciales del nombre. Por ejemplo, la propiedad parts produce una secuencia de segmentos de ruta analizados en función del valor del separador de ruta.

pathlib_parts.py
import pathlib

p = pathlib.PurePosixPath('/usr/local')
print(p.parts)

La secuencia es una tupla, que refleja la inmutabilidad de la instancia de ruta.

$ python3 pathlib_parts.py

('/', 'usr', 'local')

Hay dos formas de navegar «hacia arriba» la jerarquía del sistema de archivos desde un objeto ruta dado. La propiedad parent se refiere a una nueva instancia de ruta para el directorio que contiene la ruta, el valor devuelto por os.path.dirname(). La propiedad parents es un iterable que produce referencias de directorio padre, yendo continuamente «hacia arriba» en el jerarquía de ruta hasta llegar a la raíz.

pathlib_parents.py
import pathlib

p = pathlib.PurePosixPath('/usr/local/lib')

print('parent: {}'.format(p.parent))

print('\nhierarchy:')
for up in p.parents:
    print(up)

El ejemplo itera sobre la propiedad parents e imprime los valores miembro.

$ python3 pathlib_parents.py

parent: /usr/local

hierarchy:
/usr/local
/usr
/

Se puede acceder a otras partes de la ruta a través de las propiedades del objeto ruta. La propiedad name contiene la última parte de la ruta, después del separador de ruta final (el mismo valor que os.path.basename() produce). La propiedad suffix contiene el valor después del separador extensión y la propiedad stem contiene la porción del nombre antes del sufijo.

pathlib_name.py
import pathlib

p = pathlib.PurePosixPath('./source/pathlib/pathlib_name.py')
print('path  : {}'.format(p))
print('name  : {}'.format(p.name))
print('suffix: {}'.format(p.suffix))
print('stem  : {}'.format(p.stem))

Aunque los valores suffix y stem son similares a los valores producidos por os.path.splitext(), los valores se basan solo en el valor de name y no de la ruta completa.

$ python3 pathlib_name.py

path  : source/pathlib/pathlib_name.py
name  : pathlib_name.py
suffix: .py
stem  : pathlib_name

Creando rutas concretas

Se pueden crear instancias de la clase concreta Path a partir de argumentos de cadena que hacen referencia al nombre (o nombre potencial) de un archivo, directorio, o enlace simbólico en el sistema de archivos. La clase también proporciona varios métodos de conveniencia para construir instancias usando lugares comúnmente usados que cambian, como el directorio de trabajo actual y el directorio de inicio del usuario.

pathlib_convenience.py
import pathlib

home = pathlib.Path.home()
print('home: ', home)

cwd = pathlib.Path.cwd()
print('cwd : ', cwd)

Ambos métodos crean instancias Path pre-pobladas con una referencia absoluta del sistema de archivos.

$ python3 pathlib_convenience.py

home:  /Users/dhellmann
cwd :  /Users/dhellmann/PyMOTW

Contenidos del directorio

Hay tres métodos para acceder a las listas de directorios para descubrir los nombres de los archivos disponibles en el sistema de archivos. iterdir() es un generador, que produce una nueva instancia Path para cada elemento en el directorio que la contiene.

pathlib_iterdir.py
import pathlib

p = pathlib.Path('.')

for f in p.iterdir():
    print(f)

Si el Path no se refiere a un directorio, iterdir() eleva NotADirectoryError.

$ python3 pathlib_iterdir.py

example_link
index.rst
pathlib_chmod.py
pathlib_convenience.py
pathlib_from_existing.py
pathlib_glob.py
pathlib_iterdir.py
pathlib_joinpath.py
pathlib_mkdir.py
pathlib_name.py
pathlib_operator.py
pathlib_ownership.py
pathlib_parents.py
pathlib_parts.py
pathlib_read_write.py
pathlib_resolve.py
pathlib_rglob.py
pathlib_rmdir.py
pathlib_stat.py
pathlib_symlink_to.py
pathlib_touch.py
pathlib_types.py
pathlib_unlink.py

Utiliza glob() para buscar solo archivos que coincidan con un patrón.

pathlib_glob.py
import pathlib

p = pathlib.Path('..')

for f in p.glob('*.rst'):
    print(f)

Este ejemplo muestra todos los archivos de entrada reStructuredText en el directorio padre del script.

$ python3 pathlib_glob.py

../about.rst
../algorithm_tools.rst
../book.rst
../compression.rst
../concurrency.rst
../cryptographic.rst
../data_structures.rst
../dates.rst
../dev_tools.rst
../email.rst
../file_access.rst
../frameworks.rst
../i18n.rst
../importing.rst
../index.rst
../internet_protocols.rst
../language.rst
../networking.rst
../numeric.rst
../persistence.rst
../porting_notes.rst
../runtime_services.rst
../text.rst
../third_party.rst
../unix.rst

El procesador global admite la exploración recursiva usando el prefijo de patrón ** o llamando a rglob() en lugar de glob().

pathlib_rglob.py
import pathlib

p = pathlib.Path('..')

for f in p.rglob('pathlib_*.py'):
    print(f)

Como este ejemplo comienza desde el directorio principal, es necesaria una búsqueda recursiva para encontrar los archivos de ejemplo que coinciden con pathlib_*.py.

$ python3 pathlib_rglob.py

../pathlib/pathlib_chmod.py
../pathlib/pathlib_convenience.py
../pathlib/pathlib_from_existing.py
../pathlib/pathlib_glob.py
../pathlib/pathlib_iterdir.py
../pathlib/pathlib_joinpath.py
../pathlib/pathlib_mkdir.py
../pathlib/pathlib_name.py
../pathlib/pathlib_operator.py
../pathlib/pathlib_ownership.py
../pathlib/pathlib_parents.py
../pathlib/pathlib_parts.py
../pathlib/pathlib_read_write.py
../pathlib/pathlib_resolve.py
../pathlib/pathlib_rglob.py
../pathlib/pathlib_rmdir.py
../pathlib/pathlib_stat.py
../pathlib/pathlib_symlink_to.py
../pathlib/pathlib_touch.py
../pathlib/pathlib_types.py
../pathlib/pathlib_unlink.py

Leyendo y escribiendo archivos

Cada instancia de Path incluye métodos para trabajar con los contenidos del archivo al que se refiere. Para recuperar de inmediato los contenidos, usa read_bytes() o read_text(). Para escribir al archivo, usa write_bytes() o write_text(). Utilizar el método open() para abrir el archivo y retener el gestor de archivo, en lugar de pasar el nombre a la función incorporada open().

pathlib_read_write.py
import pathlib

f = pathlib.Path('example.txt')

f.write_bytes('This is the content'.encode('utf-8'))

with f.open('r', encoding='utf-8') as handle:
    print('read from open(): {!r}'.format(handle.read()))

print('read_text(): {!r}'.format(f.read_text('utf-8')))

Los métodos de conveniencia hacen algún tipo de comprobación antes de abrir el archivo y escribir, pero de lo contrario son equivalentes a hacer la operación directamente.

$ python3 pathlib_read_write.py

read from open(): 'This is the content'
read_text(): 'This is the content'

Manipulando directorio y enlaces simbólicos

Las rutas que representan directorios o enlaces simbólicos que no existen pueden ser utilizadas para crear las entradas asociadas del sistema de archivos.

pathlib_mkdir.py
import pathlib

p = pathlib.Path('example_dir')

print('Creating {}'.format(p))
p.mkdir()

Si la ruta ya existe, mkdir() eleva un FileExistsError.

$ python3 pathlib_mkdir.py

Creating example_dir

$ python3 pathlib_mkdir.py

Creating example_dir
Traceback (most recent call last):
  File "pathlib_mkdir.py", line 16, in <module>
    p.mkdir()
  File ".../lib/python3.5/pathlib.py", line 1214, in mkdir
    self._accessor.mkdir(self, mode)
  File ".../lib/python3.5/pathlib.py", line 371, in wrapped
    return strfunc(str(pathobj), *args)
FileExistsError: [Errno 17] File exists: 'example_dir'

Usa symlink_to() para crear un enlace simbólico. El enlace será nombrado en base al valor de la ruta y se referirá al nombre dado como argumento para symlink_to().

pathlib_symlink_to.py
import pathlib

p = pathlib.Path('example_link')

p.symlink_to('index.rst')

print(p)
print(p.resolve().name)

Este ejemplo crea un enlace simbólico, luego usa resolve() para leer el enlace para encontrar a lo que apunta e imprimir el nombre.

$ python3 pathlib_symlink_to.py

example_link
index.rst

Tipos de archivos

Una instancia de Path incluye varios métodos para probar el tipo de archivo referido por la ruta. Este ejemplo crea varios archivos de diferentes tipos y los prueba, así como algunos otros archivos específicos a dispositivos disponibles en el sistema operativo local.

pathlib_types.py
import itertools
import os
import pathlib

root = pathlib.Path('test_files')

# Clean up from previous runs.
if root.exists():
    for f in root.iterdir():
        f.unlink()
else:
    root.mkdir()

# Create test files
(root / 'file').write_text(
    'This is a regular file', encoding='utf-8')
(root / 'symlink').symlink_to('file')
os.mkfifo(str(root / 'fifo'))

# Check the file types
to_scan = itertools.chain(
    root.iterdir(),
    [pathlib.Path('/dev/disk0'),
     pathlib.Path('/dev/console')],
)
hfmt = '{:18s}' + ('  {:>5}' * 6)
print(hfmt.format('Name', 'File', 'Dir', 'Link', 'FIFO', 'Block',
                  'Character'))
print()

fmt = '{:20s}  ' + ('{!r:>5}  ' * 6)
for f in to_scan:
    print(fmt.format(
        str(f),
        f.is_file(),
        f.is_dir(),
        f.is_symlink(),
        f.is_fifo(),
        f.is_block_device(),
        f.is_char_device(),
    ))

Cada uno de los métodos, is_dir(), is_file(), is_symlink(), is_socket(), is_fifo(), is_block_device(), y is_char_device(), no acepta argumentos.

$ python3 pathlib_types.py

Name                 File    Dir   Link   FIFO  Block  Character

test_files/fifo       False  False  False   True  False  False
test_files/file        True  False  False  False  False  False
test_files/symlink     True  False   True  False  False  False
/dev/disk0            False  False  False  False   True  False
/dev/console          False  False  False  False  False   True

Propiedades de archivos

Se puede acceder a información detallada sobre un archivo utilizando los métodos stat() o lstat() (para verificar el estado de algo que podría ser un enlace simbólico). Estos métodos producen los mismos resultados que os.stat() y os.lstat().

pathlib_stat.py
import pathlib
import sys
import time

if len(sys.argv) == 1:
    filename = __file__
else:
    filename = sys.argv[1]

p = pathlib.Path(filename)
stat_info = p.stat()

print('{}:'.format(filename))
print('  Size:', stat_info.st_size)
print('  Permissions:', oct(stat_info.st_mode))
print('  Owner:', stat_info.st_uid)
print('  Device:', stat_info.st_dev)
print('  Created      :', time.ctime(stat_info.st_ctime))
print('  Last modified:', time.ctime(stat_info.st_mtime))
print('  Last accessed:', time.ctime(stat_info.st_atime))

La salida variará dependiendo de cómo fue instalad el código del ejemplo. Intenta pasar diferentes nombres de archivo en la línea de comando a pathlib_stat.py.

$ python3 pathlib_stat.py

pathlib_stat.py:
  Size: 607
  Permissions: 0o100644
  Owner: 527
  Device: 16777218
  Created      : Thu Dec 29 12:25:25 2016
  Last modified: Thu Dec 29 12:25:25 2016
  Last accessed: Thu Dec 29 12:25:34 2016

$ python3 pathlib_stat.py index.rst

index.rst:
  Size: 19363
  Permissions: 0o100644
  Owner: 527
  Device: 16777218
  Created      : Thu Dec 29 11:27:58 2016
  Last modified: Thu Dec 29 11:27:58 2016
  Last accessed: Thu Dec 29 12:25:33 2016

Para un acceso más simple a la información sobre el propietario de un archivo, usa owner() y group().

pathlib_ownership.py
import pathlib

p = pathlib.Path(__file__)

print('{} is owned by {}/{}'.format(p, p.owner(), p.group()))

Mientras stat() devuelve valores numéricos del ID del sistema, estos métodos buscan el nombre asociado con los ID.

$ python3 pathlib_ownership.py

pathlib_ownership.py is owned by dhellmann/dhellmann

El método touch() funciona como el comando touch de Unix para crear un archivo o actualizar el tiempo de modificación de un archivo existente y los permisos

pathlib_touch.py
import pathlib
import time

p = pathlib.Path('touched')
if p.exists():
    print('already exists')
else:
    print('creating new')

p.touch()
start = p.stat()

time.sleep(1)

p.touch()
end = p.stat()

print('Start:', time.ctime(start.st_mtime))
print('End  :', time.ctime(end.st_mtime))

Al ejecutar este ejemplo más de una vez, se actualiza el archivo existente en ejecuciones subsiguientes.

$ python3 pathlib_touch.py

creating new
Start: Thu Dec 29 12:25:34 2016
End  : Thu Dec 29 12:25:35 2016

$ python3 pathlib_touch.py

already exists
Start: Thu Dec 29 12:25:35 2016
End  : Thu Dec 29 12:25:36 2016

Permisos

En los sistemas tipo Unix, los permisos de archivos se pueden cambiar usando chmod(), pasando el modo como un entero. Los valores de modo pueden ser construidos utilizando constantes definidas en el módulo stat. Este ejemplo alterna el bit de permiso de ejecución del usuario.

pathlib_chmod.py
import os
import pathlib
import stat

# Create a fresh test file.
f = pathlib.Path('pathlib_chmod_example.txt')
if f.exists():
    f.unlink()
f.write_text('contents')

# Determine what permissions are already set using stat.
existing_permissions = stat.S_IMODE(f.stat().st_mode)
print('Before: {:o}'.format(existing_permissions))

# Decide which way to toggle them.
if not (existing_permissions & os.X_OK):
    print('Adding execute permission')
    new_permissions = existing_permissions | stat.S_IXUSR
else:
    print('Removing execute permission')
    # use xor to remove the user execute permission
    new_permissions = existing_permissions ^ stat.S_IXUSR

# Make the change and show the new value.
f.chmod(new_permissions)
after_permissions = stat.S_IMODE(f.stat().st_mode)
print('After: {:o}'.format(after_permissions))

El script asume que tiene los permisos necesarios para modificar el modo del archivo cuando se ejecuta.

$ python3 pathlib_chmod.py

Before: 644
Adding execute permission
After: 744

Eliminando

Hay dos métodos para eliminar cosas del sistema de archivos, dependiendo del tipo. Para eliminar un directorio vacío, usa rmdir().

pathlib_rmdir.py
import pathlib

p = pathlib.Path('example_dir')

print('Removing {}'.format(p))
p.rmdir()

Se genera una excepción FileNotFoundError si las condiciones posteriores ya se cumplen y el directorio no existe. Es también es un error intentar eliminar un directorio que no está vacío.

$ python3 pathlib_rmdir.py

Removing example_dir

$ python3 pathlib_rmdir.py

Removing example_dir
Traceback (most recent call last):
  File "pathlib_rmdir.py", line 16, in <module>
    p.rmdir()
  File ".../lib/python3.5/pathlib.py", line 1262, in rmdir
    self._accessor.rmdir(self)
  File ".../lib/python3.5/pathlib.py", line 371, in wrapped
    return strfunc(str(pathobj), *args)
FileNotFoundError: [Errno 2] No such file or directory:
'example_dir'

Para archivos, enlaces simbólicos y la mayoría de los otros tipos de ruta usa unlink().

pathlib_unlink.py
import pathlib

p = pathlib.Path('touched')

p.touch()

print('exists before removing:', p.exists())

p.unlink()

print('exists after removing:', p.exists())

El usuario debe tener permiso para eliminar el archivo, enlace simbólico, socket u otro objeto del sistema de archivos.

$ python3 pathlib_unlink.py

exists before removing: True
exists after removing: False

Ver también

  • Standard library documentation for pathlib
  • os.path – Manipulación de nombres de archivo independiente de la plataforma.
  • os-stat – Discusión sobre os.stat() y os.lstat().
  • glob – Coincidencia de patrón de shell Unix para nombres de archivo.
  • PEP 428 – El módulo pathlib