Почему root, возвращаемый из os.walk(), содержит / в качестве разделителя каталогов, но os.sep (или os.path.sep) возвращает \ в Win10?

Почему корневой элемент, возвращаемый из os.walk(), показывает / как разделитель каталогов, но os.sep (или os.path.sep ) показывает \ на Win10?

Я просто пытаюсь создать полный путь для набора файлов в папке следующим образом:

import os

base_folder = "c:/data/MA Maps"
for root, dirs, files in os.walk(base_folder):
    for f in files:
        if f.endswith(".png") and f.find("_N") != -1:
            print(os.path.join(root, f))

print(os.path.sep)

Вот что я получаю на выходе:

c:/data/MA Maps\Map_of_Massachusetts_Nantucket_County.png
c:/data/MA Maps\Map_of_Massachusetts_Norfolk_County.png
\

Я понимаю, что некоторые библиотечные функции Python (например, open()) будут работать со смешанными разделителями путей (по крайней мере, в Windows), но полагаться на этот хак действительно нельзя во всех библиотеках. Просто кажется, что элементы возвращаются из os.walk() и os.path (.sep или .join()< /strong>) должны давать согласованные результаты в зависимости от используемой операционной системы. Кто-нибудь может объяснить, почему происходит это несоответствие?

P.S. - Я знаю, что существует более согласованная библиотека для работы с путями к файлам (и множества других операций с файлами), называемая pathlib, которая была введена в Python 3.4 и, кажется, исправляет все это. Если ваш код используется в версии 3.4 или выше, лучше ли использовать методы pathlib для решения этой проблемы? Но если ваш код предназначен для систем, использующих Python до версии 3.4, как лучше всего решить эту проблему?

Вот хорошее базовое объяснение pathlib: Краткий совет по Python 3: простой способ работать с путями к файлам в Windows, Mac и Linux

Вот мой код и результат с использованием pathlib:

import os
from pathlib import Path

# All of this should work properly for any OS. I'm running Win10.

# You can even mix up the separators used (i.e."c:\data/MA Maps") and pathlib still
# returns the consistent result given below.
base_folder = "c:/data/MA Maps" 

for root, dirs, files in os.walk(base_folder):
    # This changes the root path provided to one using the current operating systems
    # path separator (/ for Win10).
    root_folder = Path(root)
    for f in files:
        if f.endswith(".png") and f.find("_N") != -1:
             # The / operator, when used with a pathlib object, just concatenates the
             # the path segments together using the current operating system path separator.
             print(root_folder / f)


c:\data\MA Maps\Map_of_Massachusetts_Nantucket_County.png
c:\data\MA Maps\Map_of_Massachusetts_Norfolk_County.png

Это можно сделать даже более лаконично, используя только pathlib и понимание списка (со всеми разделителями путей, корректно обрабатываемыми для каждой используемой ОС):

from pathlib import Path

base_folder = "c:/data/MA Maps"
path = Path(base_folder)
files = [item for item in path.iterdir() if item.is_file() and 
                                            str(item).endswith(".png") and 
                                            (str(item).find("_N") != -1)]
for file in files:
    print(file)


c:\data\MA Maps\Map_of_Massachusetts_Nantucket_County.png
c:\data\MA Maps\Map_of_Massachusetts_Norfolk_County.png

Это очень Pythonic, и, по крайней мере, я чувствую, что это довольно легко читать и понимать. .itertir() действительно мощная функция, которая делает работу с файлами и каталогами достаточно простой и кросс-платформенной. Что вы думаете?


person Sealyons    schedule 16.04.2020    source источник
comment
... это было введено в python 3.4, и, похоже, все это исправлено .. — что, если сопровождающие Python знали об этой проблеме и что является исправлением, которое вы ищете?   -  person Jongware    schedule 16.04.2020
comment
Но это по-прежнему оставляет os и path несогласованными, и, по крайней мере, насколько мне известно, pathlib не используется последовательно. В большинстве тренингов по работе с файлами Python обсуждается только использование os и пути.   -  person Sealyons    schedule 16.04.2020
comment
Windows появилась позже и выбрала другой разделитель пути \ для авторских прав. Но он поддерживал совместимость с POSIX, позволяя также использовать / в качестве разделителя пути. Вы использовали жестко заданный начальный каталог с /, но каталог проходит по добавленным подкаталогам со стандартным для ОС \ . Вы можете нормализовать, получить абсолютный путь к вашему жестко запрограммированному стартовому каталогу.   -  person Joop Eggen    schedule 16.04.2020
comment
Полностью понять историю и почему существует смешение / и \. Но не понимаю, почему os и path не дают согласованных результатов в зависимости от используемой ОС. Как показано, pathlib работает со всем этим скрытно и всегда предоставляет пути с правильными результатами, независимо от используемой ОС. Похоже, os и path должны делать то же самое.   -  person Sealyons    schedule 16.04.2020
comment
Похоже, что root (возвращенный из os.walk()) должен возвращать правильно разделенную корневую папку на основе текущей ОС (независимо от того, как разделены пути base_folder). Если бы это было так, то root и os.path.sep всегда были бы согласованы, и вы всегда были бы уверены, что ваши пути не зависят от ОС. Я думаю, что этот тип изменения ОС не должен вызывать проблем с существующим кодом (но я мог что-то упустить).   -  person Sealyons    schedule 16.04.2020


Ответы (1)


Функция os.walk всегда возвращает начальную часть dirpath без изменений по сравнению с тем, что вы ей передаете. Он не пытается нормализовать сами разделители, он просто сохраняет то, что вы ему дали. Он использует стандартные системные разделители для остальной части пути, поскольку объединяет имя каждого подкаталога с корневым каталогом с помощью os.path.join. Вы можете увидеть текущую версию реализации функции os.walk в исходный репозиторий CPython.

Один из вариантов нормализации разделителей в вашем выводе — нормализовать базовый путь, который вы передаете, в os.walk, возможно, используя pathlib. Если вы нормализуете начальный путь, все выходные данные должны автоматически использовать разделители системных путей, поскольку это будет нормализованный путь, который будет сохранен при рекурсивном обходе, а не нестандартный. Вот очень простое преобразование вашего первого блока кода, чтобы нормализовать base_folder с помощью pathlib, сохраняя при этом весь остальной код в его простоте. Лучше ли это, чем ваша версия, использующая больше функций pathlib, — это решение, которое я оставляю на ваше усмотрение.

import os
from pathlib import Path

base_folder = Path("c:/data/MA Maps")    # this will be normalized when converted to a string
for root, dirs, files in os.walk(base_folder):
    for f in files:
        if f.endswith(".png") and f.find("_N") != -1:
            print(os.path.join(root, f))
person Blckknght    schedule 16.04.2020