Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 471 – os.scandir() 함수 – 더 나은 고속 디렉터리 이터레이터

Author:
Ben Hoyt <benhoyt at gmail.com>
BDFL-Delegate:
Victor Stinner <vstinner at python.org>
Status:
Final
Type:
Standards Track
Created:
30-May-2014
Python-Version:
3.5
Post-History:
27-Jun-2014, 08-Jul-2014, 14-Jul-2014

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP에서는 표준 라이브러리에 새로운 디렉터리 반복 함수인 os.scandir()를 포함할 것을 제안합니다. 이 새로운 함수는 유용한 기능을 추가하며, 대부분의 경우 os.stat()호출을 피함으로써 os.walk()의 속도를 2~20배 향상합니다(플랫폼과 파일 시스템에 따라 다름).

근거

Python에 내장된 os.walk()는 각 디렉터리에서 os.listdir()를 호출하는 것 외에도 각 파일에서 stat()시스템 호출 또는 GetFileAttributes()를 실행하여 해당 항목이 디렉터리인지 여부를 확인하기 때문에, 필요한 것보다 상당히 느립니다.

그러나 기본 시스템 호출인 Windows의 FindFirstFile / FindNextFile 및 POSIX 시스템의 readdir는 이미 반환된 파일이 디렉터리인지 여부를 알려 주므로 추가 시스템 호출이 필요하지 않습니다. 또한 Windows 시스템 호출은 파일 크기와 최종 수정 시간 등 디렉터리 항목에 대한 stat_result객체의 모든 정보를 반환합니다.

간단히 말해, 트리의 전체 파일 및 디렉터리 수를 N이라고 할 때 os.walk()와 같은 트리 함수에 필요한 시스템 호출 수를 대략 2N에서 N으로 줄일 수 있습니다. (그리고 디렉터리 트리는 일반적으로 깊이보다 너비가 더 크므로 실제로는 이보다 훨씬 더 나은 경우가 많습니다.)

실제로 이러한 추가 시스템 호출을 모두 제거하면 os.walk()Windows에서 약 8~9배 빠르고, POSIX 시스템에서 약 2~3배 빠릅니다. 따라서 여기서 말하는 것은 마이크로 최적화가 아닙니다. benchmarks here에서 더 자세한 벤치마크를 확인하십시오.

이와 다소 관련하여, 많은 사람(Python의 Issue 11406을 참조)도 한꺼번에 하나의 큰 목록으로 반환하는 대신 반복하면서 파일 이름을 생성하는 os.listdir()의 버전을 원하고 있습니다. 이는 매우 큰 디렉터리를 반복할 때 메모리 효율을 향상합니다.

따라서 직접 호출할 수 있는 scandir()이터레이터 함수를 제공하는 것뿐만 아니라, Python의 기존 os.walk()함수도 매우 크게 빠르게 만들 수 있습니다.

구현

이 제안의 구현은 Ben Hoyt(초기 버전)와 Tim Golden(C 확장 모듈을 크게 지원)이 작성했습니다. 이 구현은 GitHub의 benhoyt/scandir에 있습니다. (구현이 이 PEP의 업데이트를 약간 늦게 따라갈 수 있습니다.)

이 모듈은 사용 및 테스트되었으므로(이 PEP의 “Use in the wild” 절 참조), 개념 증명 이상의 것입니다. 그러나 베타 소프트웨어로 표시되어 있으며 광범위한 실전 테스트를 거치지는 않았습니다. 표준 라이브러리에 포함되기 전에 일부 정리와 더 철저한 테스트가 필요하며, posixmodule.c와의 통합도 필요합니다.

제안의 세부 사항

os.scandir()

구체적으로 이 PEP에서는 표준 라이브러리의 os 모듈에 단일 함수인 scandir를 추가하고, 이 함수가 인수로 선택적 문자열 하나를 받도록 할 것을 제안합니다:

scandir(path='.') -> generator of DirEntry objects

listdir와 마찬가지로 scandir는 운영 체제의 디렉터리 반복 시스템 호출을 사용하여 지정된 path에 있는 파일의 이름을 가져오지만, 다음 두 가지 점에서 listdir와 다릅니다.

  • 단순한 파일 이름 문자열을 반환하는 대신, 파일 이름 문자열을 보유하고 운영 체제가 반환했을 수 있는 추가 데이터에 액세스할 수 있는 간단한 메서드를 제공하는 가벼운 DirEntry객체를 반환합니다.
  • 목록 대신 제너레이터를 반환하므로 scandir는 전체 목록을 즉시 반환하는 대신 진정한 이터레이터로 동작합니다.

scandir()path에 있는 각 파일과 하위 디렉터리에 대해 DirEntry객체를 생성합니다. listdir와 마찬가지로 '.''..'의사 디렉터리는 건너뛰며, 항목은 시스템에 따라 결정되는 순서로 생성됩니다. 각 DirEntry객체에는 다음과 같은 속성과 메서드가 있습니다.

  • name: scandir의 path 인수에 상대적인 항목의 파일 이름입니다(os.listdir의 반환 값에 해당합니다).
  • path: 항목의 전체 경로 이름(반드시 절대 경로일 필요는 없음) – os.path.join(scandir_path, entry.name)과 동일합니다
  • inode(): 항목의 inode 번호를 반환합니다. 결과는 DirEntry 객체에 캐시되며, 최신 정보를 가져오려면 os.stat(entry.path, follow_symlinks=False).st_ino를 사용합니다. Unix에서는 시스템 호출이 필요하지 않습니다.
  • is_dir(*, follow_symlinks=True): pathlib.Path.is_dir()와 유사하지만 반환값이 DirEntry 객체에 캐시되며, 대부분의 경우 시스템 호출이 필요하지 않습니다. follow_symlinks가 False이면 심볼릭 링크를 따라가지 않습니다.
  • is_file(*, follow_symlinks=True): pathlib.Path.is_file()과 유사하지만 반환값이 DirEntry 객체에 캐시되며, 대부분의 경우 시스템 호출이 필요하지 않습니다. follow_symlinks가 False이면 심볼릭 링크를 따라가지 않습니다.
  • is_symlink(): pathlib.Path.is_symlink()과 유사하지만 반환값이 DirEntry 객체에 캐시되며, 대부분의 경우 시스템 호출이 필요하지 않습니다.
  • stat(*, follow_symlinks=True): os.stat()과 같지만 반환값이 DirEntry 객체에 캐시되며, Windows에서는 심볼릭 링크를 제외하면 시스템 호출이 필요하지 않습니다. follow_symlinks가 False이면 os.lstat()처럼 심볼릭 링크를 따라가지 않습니다.

모든 메서드는 경우에 따라 시스템 호출을 수행할 수 있으므로 OSError를 발생시킬 수 있습니다. 자세한 내용은 “예외 처리 관련 참고 사항” 절을 참조하십시오.

일관성을 위해 가능한 경우 DirEntry 속성 및 메서드 이름을 새 pathlib 모듈의 이름과 동일하게 선택했습니다. 기능상의 유일한 차이점은 DirEntry 메서드가 처음 호출된 후 항목 객체에 값을 캐시한다는 점입니다.

os 모듈의 다른 함수와 마찬가지로 scandir()path 매개변수에 bytes 객체나 str 객체를 사용할 수 있으며, DirEntry.nameDirEntry.path 속성을 path와 동일한 형식으로 반환합니다. 그러나 유니코드 파일 이름에 대한 플랫폼 간 지원을 보장하므로 str 형식을 사용할 것을 강력히 권장합니다. (Windows에서는 bytes 파일 이름이 Python 3.3부터 더 이상 사용되지 않습니다.)

os.walk()

이 제안의 일환으로 os.walk()listdir()os.path.isdir() 대신 scandir()을 사용하도록 수정됩니다. 이렇게 하면 시스템에 따라 os.walk()의 속도가 매우 크게 향상됩니다(위에서 언급했듯이 2~20배).

예제

먼저 DirEntry.name 속성과 DirEntry.is_dir() 메서드의 사용을 보여 주는 매우 간단한 scandir() 예제입니다.:

def subdirs(path):
    """Yield directory names not starting with '.' under given path."""
    for entry in os.scandir(path):
        if not entry.name.startswith('.') and entry.is_dir():
            yield entry.name

subdirs() 함수는 Windows와 POSIX 시스템 모두에서 os.listdir()os.path.isdir()보다 훨씬 빠르며, 특히 중간 크기 이상의 디렉터리에서 그렇습니다.

또는 디렉터리 트리 내 파일의 전체 크기를 구하면서 DirEntry.stat() 메서드와 DirEntry.path 속성의 사용을 보여 주는 예제입니다.:

def get_tree_size(path):
    """Return total size of files in given path and subdirs."""
    total = 0
    for entry in os.scandir(path):
        if entry.is_dir(follow_symlinks=False):
            total += get_tree_size(entry.path)
        else:
            total += entry.stat(follow_symlinks=False).st_size
    return total

또한 이 예제는 is_dir()follow_symlinks 매개변수 사용법을 보여 줍니다. 이와 같은 재귀 함수에서는 링크를 따라가지 않는 편이 좋습니다. (이와 같은 재귀 함수에서 링크를 제대로 따라가려면 심볼릭 링크를 따라간 결과 재귀 루프가 발생하는 경우를 특별히 처리해야 합니다.)

get_tree_size()는 Windows에서 크게 빨라집니다. 추가 stat 호출이 필요하지 않기 때문입니다. 그러나 POSIX 시스템에서는 디렉터리 반복 함수가 크기 정보를 반환하지 않으므로 이 함수는 그곳에서 아무런 이점도 얻지 못합니다.

캐싱 관련 참고 사항

DirEntry 객체는 비교적 단순합니다. namepath 속성은 항상 캐시되는 것이 분명하며, is_Xstat 메서드는 값을 캐시하고(Windows에서는 FindNextFile을 통해 즉시 캐시하며, POSIX 시스템에서는 처음 사용할 때 stat 시스템 호출을 통해 캐시함) 시스템에서 다시 가져오지 않습니다.

이러한 이유로 DirEntry 객체는 반복 작업 후 사용하고 버리도록 설계되었으며, 오래 유지되는 자료 구조에 저장한 뒤 메서드를 반복해서 호출하도록 설계된 것이 아닙니다.

개발자가 “새로 고침” 동작을 원한다면(예를 들어 파일 크기 변화를 감시하려는 경우), pathlib.Path 객체를 사용하거나 호출할 때마다 운영 체제에서 최신 데이터를 가져오는 일반적인 os.stat() 또는 os.path.getsize() 함수를 호출하면 됩니다.

예외 처리 관련 참고 사항

DirEntry.is_X()DirEntry.stat()은 속성이나 프로퍼티가 아니라 명시적으로 메서드입니다. 이는 해당 작업이 저렴하지 않을 수 있고(실제로는 저렴한 경우가 많지만) 시스템 호출을 수행할 수 있음을 분명히 하기 위한 것입니다. 따라서 이러한 메서드는 OSError를 발생시킬 수 있습니다.

예를 들어 POSIX 기반 시스템에서는 DirEntry.stat()이 항상 시스템 호출을 수행하며, readdir()d_type을 지원하지 않거나 특정 조건 또는 특정 파일 시스템에서 발생할 수 있는 DT_UNKNOWN 값을 가진 d_type을 반환하는 경우 해당 시스템의 DirEntry.is_X() 메서드는 stat() 시스템 호출을 수행합니다.

이는 대개 문제가 되지 않습니다. 예를 들어 표준 라이브러리에 정의된 os.walk()listdir() 호출 주변의 오류만 포착합니다.

또한, DirEntry.is_X 메서드의 예외 발생 동작은 pathlib의 동작과 일치하므로 – 권한 또는 기타 치명적인 오류가 발생한 경우에만 OSError를 발생시키고, 경로가 존재하지 않거나 끊어진 심볼릭 링크인 경우에는 False를 반환하므로 – is_X() 호출 주변에서 오류를 잡을 필요가 없는 경우가 많습니다.

그러나 사용자가 세밀한 오류 처리를 필요로 하는 경우에는 모든 메서드 호출 주변에서 OSError를 포착하고 적절히 처리하는 것이 바람직할 수 있습니다.

예를 들어, 아래에는 위에서 보인 get_tree_size() 예제에 세밀한 오류 처리를 추가한 버전이 나와 있습니다.:

def get_tree_size(path):
    """Return total size of files in path and subdirs. If
    is_dir() or stat() fails, print an error message to stderr
    and assume zero size (for example, file has been deleted).
    """
    total = 0
    for entry in os.scandir(path):
        try:
            is_dir = entry.is_dir(follow_symlinks=False)
        except OSError as error:
            print('Error calling is_dir():', error, file=sys.stderr)
            continue
        if is_dir:
            total += get_tree_size(entry.path)
        else:
            try:
                total += entry.stat(follow_symlinks=False).st_size
            except OSError as error:
                print('Error calling stat():', error, file=sys.stderr)
    return total

지원

GitHub의 scandir 모듈은 포크되어 상당히 많이 사용되었지만(이 PEP의 “실제 사용 사례”를 참조하십시오), python-dev 및 python-ideas 메일링 리스트의 핵심 개발자와 다른 사람들로부터 scandir와 유사한 함수에 대한 직접적인 지원도 상당히 많았습니다. 몇 가지 예를 들면 다음과 같습니다.

  • python-dev: 2014년 6월의 이 python-dev 스레드에서 scandir 및 PEP 471에 대한 찬성 의견은 상당히 많았고 반대 의견은 매우 적었습니다.
  • Alyssa Coghlan, 핵심 Python 개발자: “네트워크에 마운트된 디렉터리 트리에서 dirent 구조체에 이미 존재하는 정보를 얻기 위해 모든 파일에 stat을 수행해야 하는 것에 대해 현지 Red Hat 릴리스 엔지니어링 팀이 불만을 표시하는 것을 보았습니다. 따라서 해당 정보를 사용할 수 있게 해 주기만 한다면 저는 os.scandir에 확실히 찬성합니다.” [source1]
  • Tim Golden, 핵심 Python 개발자는 scandir을 충분히 지지하여 scandir의 C 확장 모듈을 리팩터링하고 크게 개선하는 데 시간을 투자했습니다. [source2]
  • Christian Heimes, 핵심 Python 개발자: “yielddir() 같은 것에 찬성합니다.” [source3] 그리고 “맞습니다! 제가 코드베이스에서 직접 만든 우회책을 제거할 수 있도록 이 기능이 3.4에 포함되기를 바랍니다.” “3.4에서 이 기능을 볼 수 있어서 제가 코드베이스에서 직접 만든 해킹을 제거할 수 있으면 좋겠습니다.” [source4]
  • Gregory P. Smith, 핵심 Python 개발자: “오늘 밤 3.4beta1이 나오므로 이것은 3.4에 포함되지 않을 예정이며, 따라서 3.5로 미루겠습니다. 위에서 설명한 제안된 설계가 정말 마음에 듭니다.” [source5]
  • Guido van Rossum은 scandir을 Python 3.5에 추가할 가능성에 대해(3.4에는 너무 늦었으므로) 다음과 같이 말했습니다. “os에 추가하든 pathlib에 추가하든 scandir()을 추가할 기회는 이미 지나갔습니다. 물론 실험하여 3.5에 검토할 수 있도록 준비하십시오. 하지만 3.4에 추가하고 싶지는 않습니다.” [source6]

이 PEP 자체에 대한 지원(메타 지원?)은 Alyssa (Nick) Coghlan이 python-dev에서 다음과 같이 제시했습니다. “이 모든 내용을 3.5용으로 검토하고 구체적인 os.scandir API를 제안하는 PEP가 있다면 좋겠습니다.” [source7]

실제 사용 사례

현재 scandir 구현은 분명히 유용하지만 “베타”로 명확히 표시되어 있으므로 실제 환경에서 얼마나 사용되고 있는지는 확실하지 않습니다. Ben Hoyt는 이를 사용하는 사람들로부터 여러 보고를 받았습니다. 예를 들면 다음과 같습니다.

  • Chris F: “상당히 큰 디렉터리를 처리하고 있으며 getdents를 수정해야 할 것이라고 반쯤 예상하고 있었습니다. 그러한 수고를 덜어 주셔서 감사합니다.” [개인 이메일을 통해]
  • bschollnick: “이 코드에서 Scandir을 구성 요소로 사용하고 있으므로 이에 대해 알려 드리고 싶었습니다. scandir이 os.listdir에 비해 성능을 획기적으로 개선한 좋은 예가 여기 있습니다.” [source8]
  • Avram L: “현재 진행 중인 프로젝트에서 scandir을 테스트하고 있습니다. 상당히 안정적인 것 같으므로, 우선 훌륭한 작업에 감사하다고 말씀드리고 싶습니다!” [개인 이메일을 통해]
  • Matt Z: “scandir을 사용하여 네트워크 디렉터리의 내용을 15초 이내에 덤프했습니다. 루트 디렉터리 13개와 구조 내 파일 60,000개였습니다. 이 기능은 정확히 같은 작업을 수행하는 데 15~20분이 걸리던 스프레드시트에 포함된 오래된 VBA 코드를 대체하게 될 것입니다.” [개인 이메일을 통해]

다른 사람들도 이를 위해 requested a PyPI package를 요청했으며, 해당 패키지가 생성되었습니다. PyPI package를 참조하십시오.

GitHub 통계가 큰 의미를 갖는 것은 아니지만, scandir에는 여러 명의 감시자와 이슈, 포크 등이 있습니다. 다음은 2014년 7월 7일 기준 통계를 바탕으로 한 현황입니다.

  • 감시자: 17명
  • 별표: 57개
  • 포크: 20
  • 이슈: 열린 4개, 닫힌 26개

또한 이 PEP는 os.walk()의 속도를 크게 향상시키므로, 수천 명의 개발자와 스크립트, 그리고 많은 프로덕션 코드가 이를 통해 이점을 얻습니다. 예를 들어 GitHub에는 os.walk의 사용 사례가 os.mkdir의 사용 사례(230,000개)와 거의 같은 수(194,000개)만큼 있습니다.

거부된 아이디어

이름 지정

이 함수의 이름에 대한 유일한 다른 유력한 후보는 iterdir()였습니다. 그러나 Python의 iterX() 함수(주로 Python 2에서 발견됨)는 대응하는 비 이터레이터 함수의 단순한 이터레이터 버전인 경우가 많습니다. 예를 들어 dict.iterkeys()는 단지 dict.keys()의 이터레이터 버전이지만, 반환되는 객체는 동일합니다. 그러나 scandir()의 경우 반환 값은 상당히 다른 객체(DirEntry 객체와 파일 이름 문자열)입니다. 따라서 이는 이름의 차이로 반영되어야 할 것이며, 그래서 scandir()이 되었습니다.

python-dev의 관련 논의를 참조하십시오.

와일드카드 지원

Windows의 FindFirstFile/FindNextFile*.jpg 같은 “와일드카드” 전달을 지원하므로, 처음에는 (이 PEP의 작성자를 포함한) 사람들이 사용자가 이를 전달할 수 있도록 scandir함수에 windows_wildcard키워드 인자를 포함하는 것이 좋겠다고 생각했습니다.

그러나 더 깊이 생각하고 논의한 결과, 플랫폼 간에 구현할 수 있는 경우가 아니라면 (pattern 키워드 인자 또는 이와 유사한 방식) 이는 좋지 않은 생각이라고 결정했습니다. 처음에는 충분히 쉬워 보입니다. Windows에서는 OS의 와일드카드 지원을 사용하고, POSIX 기반 시스템에서는 그 후에 fnmatchre같은 것을 사용하면 됩니다.

안타깝게도 정확한 Windows 와일드카드 일치 규칙은 Microsoft가 어디에도 제대로 문서화하지 않았으며 매우 특이합니다(이 블로그 게시물 참조). 따라서 fnmatch나 정규식을 사용해 이를 에뮬레이트하는 것은 매우 문제가 많습니다.

따라서 합의된 결론은 Windows 와일드카드 지원이 좋지 않은 아이디어라는 것이었습니다. 플랫폼 간에 이를 구현할 방법이 있다면 나중에 추가할 수는 있지만, 초기 버전에는 포함하지 않습니다.

이 2012년 11월 python-ideas 스레드PEP 471에 관한 2014년 6월 python-dev 스레드에서 더 자세히 알아보십시오.

기본적으로 심볼릭 링크를 따르지 않는 메서드

DirEntry 메서드가 심볼릭 링크를 따라야 하는지 여부(is_X() 메서드에 follow_symlinks 매개변수가 없는 경우)를 두고 python-dev에서 많은 논쟁이 있었습니다(이 스레드의 메시지를 참조하십시오).

처음에는 심볼릭 링크를 따르지 않았지만(이 PEP의 이전 버전과 scandir.py 모듈 참조), Victor Stinner는 python-dev에서 기본적으로 심볼릭 링크를 따르는 것이 더 낫다는 매우 설득력 있는 주장을 펼쳤습니다. 그 이유는 다음과 같습니다.

  • 링크를 따르는 것이 일반적으로 원하는 동작입니다(표준 라이브러리에서 os.listdir()os.path.isdir()을 사용하는 함수의 92%가 심볼릭 링크를 따릅니다).
  • 이는 유사한 함수인 os.path.isdir()pathlib.Path.is_dir()이 정립한 선례이므로, 다르게 동작하면 혼란을 일으킵니다.
  • 링크를 따르지 않는 접근 방식을 사용하면 링크를 따르고 싶을 때 if (entry.is_symlink() and os.path.isdir(entry.path)) or entry.is_dir()와 같이 작성해야 하므로 번거롭습니다.

심볼릭 링크를 따르지 않는 버전이 오류가 발생하기 쉽다는 것을 보여 주는 사례로, 이 PEP의 작성자는 scandir.py에서 scandir.walk()을 처음 구현할 때 바로 이 테스트를 잘못 작성하여 버그를 만들었습니다(여기 Issue #4를 참조하십시오).

결국 메서드가 심볼릭 링크를 따라야 한다는 데 완전한 합의가 있었던 것은 아니지만, 가장 깊이 관여한 참가자들 사이에는 기본적인 합의가 있었으며, 이 PEP의 작성자는 위 사례가 기본적으로 심볼릭 링크를 따르는 것을 정당화할 만큼 충분히 강력하다고 생각합니다.

또한 다른 동작을 원한다면 관련 메서드를 follow_symlinks=False로 호출하는 것은 간단합니다.

프로퍼티인 DirEntry 속성

어떤 면에서는 DirEntryis_X()stat()를 메서드가 아니라 프로퍼티로 만드는 편이 더 좋을 수 있습니다. 이를 통해 해당 연산이 매우 저렴하거나 비용이 없음을 나타낼 수 있기 때문입니다. 그러나 실제로는 완전히 그렇지 않습니다. POSIX 기반 시스템에서는 stat()에 OS 호출이 필요하지만 Windows에서는 필요하지 않기 때문입니다. is_dir()와 그와 유사한 메서드도 일부 파일 시스템에서 dirent.d_type 값이 DT_UNKNOWN인 경우 POSIX 기반 시스템에서 OS 호출을 수행할 수 있습니다.

또한, 사용자는 entry.is_dir속성 접근이 시스템 호출을 내부적으로 수행하는 경우에도 OSError가 아니라 오직 AttributeError만 발생시키기를 기대합니다. 호출하는 코드는 단순한 속성 접근처럼 보이는 부분을 try/except로 감싸야 하므로, 이를 메서드로 만드는 것이 훨씬 낫습니다.

this May 2013 python-dev thread를 참조하십시오. 여기에서 이 PEP의 작성자가 이러한 주장을 제시했으며 핵심 개발자들의 동의가 있습니다.

“정적” 속성 전용 객체인 DirEntry 필드입니다.

this July 2014 python-dev message에서 Paul Moore는 “운영 체제 기능을 얇게 감싼 래퍼”인 해결책을 제안했습니다. 여기서 DirEntry객체에는 정적 속성인 name, path, is_X만 있었고, st_X 속성은 Windows에서만 존재했습니다. 더 단순한 저수준 함수를 고수준 함수의 구성 요소로 사용하자는 아이디어였습니다.

처음에는 이러한 방식으로 단순화하는 것이 좋은 일이라는 데 전반적인 동의가 있었습니다. 그러나 이 접근 방식에는 두 가지 문제가 있었습니다. 첫째, is_dir및 이와 유사한 속성이 POSIX에서 항상 존재한다는 가정인데, d_type이 없거나 DT_UNKNOWN인 경우에는 그렇지 않습니다. 둘째, 실제로는 훨씬 사용하기 어려운 API입니다. POSIX에서는 is_dir속성조차 항상 존재하지 않으므로 hasattr()로 테스트한 다음, 해당 속성이 없으면 os.stat()을 호출해야 하기 때문입니다.

this July 2014 python-dev response에서 이 PEP의 작성자는 이 선택지가 이상적이지 않은 해결책인 이유를 자세히 설명했으며, 이어진 Paul Moore의 답변에서도 동의의 뜻을 밝혔습니다.

ensure_lstat 옵션을 사용하며 정적인 DirEntry 필드입니다.

Alyssa Coghlan은 이 June 2014 python-dev message에서 또 다른, 겉보기에는 더 단순하고 매력적인 선택지를 제안했습니다. DirEntry.is_XDirEntry.lstat_result를 속성으로 만들고, 반복 시점에 DirEntry.lstat_result를 채우되, scandir()호출에서 새 인자 ensure_lstat=True가 지정된 경우에만 그렇게 하자는 것입니다.

이 방식은 필요한 경우 scandir()에서 stat 결과를 쉽게 얻을 수 있다는 점에서 앞선 방식보다 유리합니다. 그러나 세밀한 오류 처리가 번거로워진다는 심각한 단점이 있습니다. 반복 중에 stat()이 호출되어 그 결과 OSError가 발생할 가능성이 있으므로, 상당히 보기 좋지 않은 수작업 반복 루프가 만들어지기 때문입니다.:

it = os.scandir(path)
while True:
    try:
        entry = next(it)
    except OSError as error:
        handle_error(path, error)
    except StopIteration:
        break

또는 이는 scandir()이 반복 중 stat()오류가 발생할 때 호출할 함수인 onerror인자를 받아들여야 한다는 뜻입니다. 이 PEP의 작성자에게 이러한 방식은 DirEntry.stat()호출을 try/except로 감싸는 것만큼 직접적이지도, 파이썬답지도 않아 보입니다.

또 다른 단점은 os.scandir()이 코드를 더 빠르게 실행하도록 작성되었다는 점입니다. POSIX에서 항상 os.lstat()을 호출해도 속도 향상은 얻을 수 없습니다. 대부분의 경우 전체 stat_result객체는 필요하지 않습니다. is_X()메서드만으로 충분하며 이 정보는 이미 알려져 있기 때문입니다.

논의를 요약하고 원래의 PEP 471제안이 결국 “올바른 것”이라고 생각하는 이유를 자세히 설명한 Ben Hoyt’s July 2014 reply를 참조하십시오.

(name, stat_result) 두-튜플인 반환 값입니다.

처음에 이 PEP의 작성자는 이 개념을 iterdir_stat()이라는 함수로 제안했으며, 이 함수는 (name, stat_result)의 두-튜플을 생성했습니다. 이 방식은 새로운 형식을 도입하지 않는다는 장점이 있습니다. 그러나 POSIX 기반 시스템에서는 stat_result가 부분적으로만 채워집니다. 대부분의 필드가 None으로 설정되고 그 밖에도 여러 특이한 동작이 있으므로, 실제로는 stat_result객체가 전혀 아니며 os.stat()과 다르다는 점을 철저히 문서화해야 합니다.

또한 Python은 속성과 메서드를 갖춘 적절한 객체를 잘 지원하므로, 두-튜플보다 더 합리적이고 단순한 API를 만들 수 있습니다. 운영 체제에 기능이 추가되고 이를 DirEntry에 포함하려 할 때, DirEntry객체를 더욱 확장하기 쉽고 미래에도 사용할 수 있게 해 줍니다.

이전 논의도 참조하십시오.

오버로드된 stat_result 객체인 반환 값입니다.

논의된 또 다른 대안은 반환값을 namepath 속성을 가진 오버로드된 stat_result 객체로 만드는 것이었습니다. 하지만 이것이 이상한(그리고 억지스러운!) 오버로딩이라는 점은 차치하더라도, 위에서 언급한 것과 같은 문제가 있습니다. POSIX 시스템에서 readdir()stat_result 정보 대부분을 가져오지 않으며, st_mode 값(의 일부)만 가져옵니다.

반환값을 pathlib.Path 객체로 만들기

Antoine Pitrou의 새로운 표준 라이브러리 pathlib 모듈이 있으니, scandir()pathlib.Path의 인스턴스를 반환하도록 하는 것이 처음에는 훌륭한 아이디어처럼 보입니다. 하지만 pathlib.Pathis_X()stat() 함수는 명시적으로 캐시되지 않는 반면, scandir은 설계상 이들을 캐시해야 합니다. 원본 디렉터리 순회 시스템 호출에서 나온 값을 (흔히) 반환하기 때문입니다.

또한 scandir이 반환하는 pathlib.Path 인스턴스는 stat 값을 캐시하는데 일반적인 pathlib.Path 객체는 명시적으로 그렇지 않다면, 이는 적잖이 혼란스러울 것입니다.

Guido van Rossum은 scandir의 맥락에서 pathlib.Path가 stat을 캐시하는 것을 여기서 명시적으로 거부했으며, 이로 인해 pathlib.Path 객체는 scandir 반환값으로 나쁜 선택이 됩니다.

가능한 개선 사항

scandir에 가할 수 있는 가능한 개선 사항은 많지만, 이 PEP의 저자가 염두에 두고 있는 몇 가지를 간략히 나열하면 다음과 같습니다:

  • scandir는 Py_BEGIN_ALLOW_THREADS 블록당 readdir/ FindNextFile을 이를테면 50회씩 호출하여 C 확장 모듈에 더 오래 머무르게 함으로써 더욱 빨라질 가능성이 있으며, 그 결과 다소 더 빨라질 수도 있습니다. 이 방식은 아직 테스트되지 않았지만, Issue 11406에서 Antoine Pitrou가 제안한 것입니다. [source9]
  • scandir는 반복마다 드는 메모리 할당 비용을 피하기 위해 프리 리스트를 사용할 수 있습니다. 10개, 어쩌면 1개짜리의 짧은 프리 리스트만으로도 도움이 될 수 있습니다. Victor Stinner가 python-dev thread on June 27에서 제안했습니다.

이전 논의