Нужно передать объект пути в API, ожидающий путьоподобное значение. Какой метод будет вызван и что напечатает программа?
import os
class DataPath:
def __fspath__(self):
return "/srv/data"
path = DataPath()
print(os.fspath(path))
Будет вызван специальный метод __fspath__, а программа напечатает /srv/data. Этот метод реализует протокол путьоподобных объектов: он должен вернуть объект типа str или bytes, описывающий путь.
Сам объект не обязан быть строкой и не наследуется от специального базового класса. Функции файловой системы распознают его по наличию корректного __fspath__.
Протокол os.PathLike появился в Python 3.6, чтобы отделить представление пути от строкового типа. До этого API часто принимали строки, а появление объектов вроде pathlib.Path требовало единого способа передавать пути в функции, работающие с файловой системой.
Функция os.fspath стала стандартной точкой преобразования: она сохраняет строки и байтовые строки как есть, а для остальных объектов вызывает __fspath__.
Если библиотека принимает путь только через str(path), она может получить не фактическое файловое представление, а произвольное отладочное или пользовательское описание объекта. Это особенно опасно, когда __str__ предназначен для логирования и не обязан возвращать корректный путь.
Неверная реализация __fspath__ приводит к ошибке во время преобразования. Метод не может вернуть произвольный объект, например другой экземпляр класса пути: результатом должны быть только str или bytes.
При вызове os.fspath(path) Python действует примерно так:
str или bytes, оно возвращается без изменений.__fspath__.str или bytes.TypeError.В примере path — экземпляр DataPath, поэтому вызывается его __fspath__:
Многие функции стандартной библиотеки, работающие с путями, используют этот протокол напрямую или через внутреннее преобразование. Для аннотаций можно наследоваться от os.PathLike, но это не обязательно для фактического участия в протоколе: достаточно корректного метода __fspath__.
Возвращать следует согласованный тип. Если путь может содержать байты, допустим bytes, но смешивание str и bytes в одной операции с путями часто приводит к TypeError. Метод также не должен выполнять операции ввода-вывода: его задача — представить путь, а не проверить существование файла.
Сервис хранит локальные файлы в объекте TenantPath, который добавляет к базовому каталогу идентификатор арендатора. Возможны три подхода.
Преобразовывать объект через str() просто, но это связывает файловый API с форматом отображения объекта и допускает случайный возврат неподходящей строки. Передавать каждому вызывающему коду отдельное поле вроде .value прозрачнее, но раскрывает внутреннее устройство класса и усложняет интеграцию с библиотеками.
Реализация __fspath__ лучше соответствует назначению объекта: файловые API получают путь через стандартный протокол, а логирование может использовать независимый __str__. В результате pathlib, open и функции os могут работать с объектом без специальных адаптеров, при этом проверка существования файла остаётся ответственностью вызывающего кода.
Можно ли вернуть из __fspath__ объект pathlib.Path?
Нет. Контракт метода требует итоговый объект типа str или bytes. Если вернуть pathlib.Path, os.fspath завершится TypeError; вложенное преобразование не выполняется автоматически.
Чем __fspath__ отличается от __str__?
__str__ задаёт строковое представление объекта для общего пользовательского вывода и не является специальным протоколом пути. __fspath__ предназначен именно для API файловой системы и имеет более строгий контракт по типу результата. Поэтому наличие __str__ само по себе не делает объект путьоподобным.
Обязательно ли наследоваться от os.PathLike, чтобы объект работал с os.fspath?
Нет. os.PathLike — удобный интерфейс и средство явного обозначения намерения, но распознавание выполняется по протоколу __fspath__. Объект с корректно реализованным методом может поддерживаться структурно, без наследования от этого класса.