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

Python 개선 제안 한국어 번역

PEP 428 – pathlib 모듈 – 객체 지향 파일 시스템 경로

Author:
Antoine Pitrou <solipsis at pitrou.net>
Status:
Final
Type:
Standards Track
Created:
30-Jul-2012
Python-Version:
3.4
Post-History:
05-Oct-2012
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

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

추상

이 PEP는 서드파티 모듈인 pathlib를 표준 라이브러리에 포함할 것을 제안합니다. 포함은 PEP 411에 설명된 대로 잠정 레이블 아래에서 제안됩니다. 따라서 API 변경은 PEP 프로세스의 일부로 수행하거나, 표준 라이브러리에 수용된 후(그리고 잠정 레이블이 제거될 때까지) 수행할 수 있습니다.

이 라이브러리의 목적은 파일 시스템 경로와 사용자가 경로에 대해 수행하는 일반적인 연산을 처리할 수 있는 단순한 클래스 계층을 제공하는 것입니다.

관련 작업

파일 시스템 경로를 위한 객체 지향 API는 이미 PEP 355에서 제안되었으나 거부되었습니다. 객체 지향 파일 시스템 경로라는 개념을 구현한 여러 서드파티 구현이 널리 존재합니다.

  • Jason Orendorff, Jason R.의 역사적인 path.py module Coombs와 그 밖의 사람들이 만든 것으로, str을 상속하는 Path 클래스를 제공합니다.
  • Twisted의 약간 특수화된 FilePath class;
  • tuple이 아니라 str을 상속하는 AlternativePathClass proposal;
  • Unipath는 str 상속 방식의 변형으로, I/O를 수행하지 않는 연산을 위한 AbstractPath 클래스와 모든 일반 연산을 위한 Path 클래스라는 두 개의 공개 클래스를 사용합니다.

이 제안은 이러한 이전 시도와 PEP 355의 거부에서 교훈을 얻으려고 합니다.

구현

이 제안의 구현은 pathlib의 Mercurial repository에 있는 pep428 브랜치에서 추적됩니다.

객체 지향 API를 사용하는 이유

전용 클래스를 사용하여 파일 시스템 경로를 나타내는 근거는 날짜, 시간 또는 IP 주소와 같은 다른 종류의 무상태 객체를 사용하는 근거와 같습니다. Python은 C 언어의 API를 엄격히 복제하는 방식에서 벗어나 모든 종류의 일반적인 기능을 더 우수하고 유용한 추상화로 제공하는 방향으로 서서히 나아가고 있습니다. 이 PEP가 수용되지 않더라도 언젠가는 다른 형태의 파일 시스템 처리 추상화가 표준 라이브러리에 채택될 가능성이 높습니다.

실제로 많은 사람은 숫자 타임스탬프와 time 모듈 API를 사용하는 대신 datetime 모듈이 제공하는 고수준 객체를 사용하여 날짜와 시간을 처리하는 것을 선호할 것입니다. 또한 전용 클래스를 사용하면 Windows 경로의 대소문자 구분 없음과 같이 바람직한 동작을 기본적으로 활성화할 수 있습니다.

제안

클래스 계층

pathlib 모듈은 단순한 클래스 계층을 구현합니다.:

                +----------+
                |          |
       ---------| PurePath |--------
       |        |          |       |
       |        +----------+       |
       |             |             |
       |             |             |
       v             |             v
+---------------+    |    +-----------------+
|               |    |    |                 |
| PurePosixPath |    |    | PureWindowsPath |
|               |    |    |                 |
+---------------+    |    +-----------------+
       |             v             |
       |          +------+         |
       |          |      |         |
       |   -------| Path |------   |
       |   |      |      |     |   |
       |   |      +------+     |   |
       |   |                   |   |
       |   |                   |   |
       v   v                   v   v
  +-----------+           +-------------+
  |           |           |             |
  | PosixPath |           | WindowsPath |
  |           |           |             |
  +-----------+           +-------------+

이 계층은 두 가지 차원에 따라 경로 클래스를 나눕니다.

  • 경로 클래스는 순수 클래스이거나 구체 클래스일 수 있습니다. 순수 클래스는 실제 I/O가 필요하지 않은 연산만 지원하며, 이러한 연산은 대부분의 경로 조작 연산입니다. 구체 클래스는 순수 클래스의 모든 연산에 더해 I/O를 수행하는 연산도 지원합니다.
  • 경로 클래스는 나타내는 운영 체제 경로의 종류에 따라 특정 특성을 가집니다. pathlib는 두 가지 특성을 구현합니다. 하나는 Windows 시스템에 구현된 파일 시스템 의미 체계를 위한 Windows 경로이고, 다른 하나는 그 밖의 시스템을 위한 POSIX 경로입니다.

모든 순수 클래스는 어떤 시스템에서도 인스턴스화할 수 있습니다. 예를 들어 Windows에서 PurePosixPath 객체를 조작하거나 Unix에서 PureWindowsPath 객체를 조작할 수 있으며, 그 반대도 가능합니다. 그러나 구체 클래스는 일치하는 시스템에서만 인스턴스화할 수 있습니다. 실제로 Unix에서 WindowsPath 객체로 I/O를 시작하거나 그 반대로 하는 것은 오류가 발생하기 쉽습니다.

또한 시스템에 따라 달라지는 팩토리 역할도 하는 두 개의 베이스 클래스가 있습니다. PurePath는 운영 체제에 따라 PurePosixPath또는 PureWindowsPath를 인스턴스화합니다. 마찬가지로 PathPosixPath또는 WindowsPath를 인스턴스화합니다.

대부분의 사용 사례에서는 Path클래스를 사용하는 것으로 충분할 것으로 예상되며, 이것이 모든 클래스 중 이름이 가장 짧은 이유입니다.

내장 타입과의 혼동 없음

이 제안에서 경로 클래스는 내장 타입에서 파생되지 않습니다. 이는 str에서 파생된 다른 일부 Path 클래스 제안과 대조됩니다. 또한 시퀀스 프로토콜을 구현하는 것처럼 가장하지 않습니다. 경로가 시퀀스로 동작하기를 원한다면 전용 속성인 parts속성을 조회해야 합니다.

str을 상속하지 않는 핵심 이유는 경로를 나타내는 문자열과 경로를 나타내지 않는 문자열을 실수로 연산하는 것을 방지하기 위해서입니다. 예를 들어 path + an_accident과 같은 연산입니다. 문자열을 사용한 연산은 유효하거나 예상되는 파일 시스템 경로로 반드시 이어지지는 않으므로, 이를 서브클래싱하지 않아 문자열과의 우발적인 연산을 피하는 것이 “암시적인 것보다 명시적인 것이 낫다”는 원칙에 부합합니다. Python 핵심 개발자가 작성한 blog post에서는 이 특정 설계 결정을 내린 이유를 더 자세히 설명합니다.

불변성

Path 객체는 불변이므로 해시할 수 있으며, 일종의 프로그래밍 오류도 방지합니다.

합리적인 동작

os.path의 기능 중 재사용되는 것은 많지 않습니다. 많은 os.path 함수는 하위 호환성 때문에 혼란스럽거나 명백히 잘못된 동작에 묶여 있습니다. 예를 들어 os.path.abspath()가 심볼릭 링크를 먼저 해석하지 않고 “..” 경로 구성 요소를 단순화한다는 점이 그렇습니다.

비교

같은 종류의 경로는 순수 경로인지 여부와 관계없이 비교할 수 있고 순서를 정할 수 있습니다.:

>>> PurePosixPath('a') == PurePosixPath('b')
False
>>> PurePosixPath('a') < PurePosixPath('b')
True
>>> PurePosixPath('a') == PosixPath('a')
True

Windows 경로 객체를 비교하고 순서를 정할 때는 대소문자를 구분하지 않습니다.:

>>> PureWindowsPath('a') == PureWindowsPath('A')
True

서로 다른 종류의 경로는 항상 같지 않은 것으로 비교되며, 순서를 정할 수 없습니다.:

>>> PurePosixPath('a') == PureWindowsPath('a')
False
>>> PurePosixPath('a') < PureWindowsPath('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: PurePosixPath() < PureWindowsPath()

경로는 내장 타입(예: str)의 인스턴스 및 그 밖의 모든 타입과 같지 않은 것으로 비교되며, 이들과 순서를 정할 수 없습니다.

유용한 표기법

API는 매직을 피하면서 유용한 표기법을 제공하려고 합니다. 몇 가지 예:

>>> p = Path('/home/antoine/pathlib/setup.py')
>>> p.name
'setup.py'
>>> p.suffix
'.py'
>>> p.root
'/'
>>> p.parts
('/', 'home', 'antoine', 'pathlib', 'setup.py')
>>> p.relative_to('/home/antoine')
PosixPath('pathlib/setup.py')
>>> p.exists()
True

순수 경로 API

PurePathAPI의 철학은 os.path와 같은 뒤죽박죽인 함수 모음을 노출하지 않고, 유용한 경로 조작 연산을 일관된 집합으로 제공하는 것입니다.

정의

먼저 몇 가지 규칙을 살펴보겠습니다.

  • 모든 경로에는 드라이브와 루트가 있을 수 있습니다. POSIX 경로에서는 드라이브가 항상 비어 있습니다.
  • 상대 경로에는 드라이브도 루트도 없습니다.
  • 루트를 가지면 POSIX 경로는 절대 경로입니다. Windows 경로는 드라이브 루트를 모두 가지면 절대 경로입니다. Windows UNC 경로(예: \\host\share\myfile.txt)는 항상 드라이브와 루트를 가집니다(여기서는 각각 \\host\share\입니다).
  • 드라이브 또는 루트 중 하나를 가진 경로를 앵커가 있는 경로라고 합니다. 해당 앵커는 드라이브와 루트를 연결한 것입니다. POSIX에서는 “앵커된”과 “절대”가 같은 의미입니다.

구성

구성과 결합은 유사한 의미론을 드러내므로 함께 설명합니다.

경로를 구성하는 가장 간단한 방법은 문자열 표현을 전달하는 것입니다.:

>>> PurePath('setup.py')
PurePosixPath('setup.py')

불필요한 경로 구분자와 "." 구성 요소는 제거됩니다.:

>>> PurePath('a///b/c/./d/')
PurePosixPath('a/b/c/d')

여러 인자를 전달하면 자동으로 결합됩니다.:

>>> PurePath('docs', 'Makefile')
PurePosixPath('docs/Makefile')

결합 의미론은 os.path.join과 유사하며, 앵커된 경로는 이전에 결합된 구성 요소의 정보를 무시합니다.:

>>> PurePath('/etc', '/usr', 'bin')
PurePosixPath('/usr/bin')

그러나 Windows 경로에서는 필요한 경우 드라이브가 유지됩니다.:

>>> PureWindowsPath('c:/foo', '/Windows')
PureWindowsPath('c:/Windows')
>>> PureWindowsPath('c:/foo', 'd:')
PureWindowsPath('d:')

또한 경로 구분자는 플랫폼 기본값으로 정규화됩니다.:

>>> PureWindowsPath('a/b') == PureWindowsPath('a\\b')
True

불필요한 경로 구분자와 "." 구성 요소는 제거되지만, ".." 구성 요소는 제거되지 않습니다.:

>>> PurePosixPath('a//b/./c/')
PurePosixPath('a/b/c')
>>> PurePosixPath('a/../b')
PurePosixPath('a/../b')

여러 개의 선행 슬래시는 경로 종류에 따라 다르게 처리됩니다. Windows 경로에서는 UNC 표기법 때문에 항상 유지됩니다.:

>>> PureWindowsPath('//some/path')
PureWindowsPath('//some/path/')

POSIX에서는 선행 슬래시가 정확히 두 개인 경우를 제외하고 축약됩니다. 정확히 두 개인 경우는 pathname resolution 의 POSIX 사양에서 특별한 경우이며, Cygwin 호환성을 위해서도 필요합니다.:

>>> PurePosixPath('///some/path')
PurePosixPath('/some/path')
>>> PurePosixPath('//some/path')
PurePosixPath('//some/path')

인자 없이 생성자를 호출하면 논리적인 “현재 디렉터리”를 가리키는 경로 객체가 생성됩니다(절대 경로를 조회하지 않으며, 구체적인 경로에서 절대 경로를 조회하는 작업은 cwd() 클래스 메서드가 담당합니다).:

>>> PurePosixPath()
PurePosixPath('.')

표현

경로를 표현하려면(예: 서드파티 라이브러리에 전달하려면) 경로에 str()을 호출하기만 하면 됩니다.:

>>> p = PurePath('/home/antoine/pathlib/setup.py')
>>> str(p)
'/home/antoine/pathlib/setup.py'
>>> p = PureWindowsPath('c:/windows')
>>> str(p)
'c:\\windows'

문자열 표현에서 슬래시를 강제로 순방향 슬래시로 사용하려면 as_posix() 메서드를 사용하십시오.:

>>> p.as_posix()
'c:/windows'

바이트 표현을 얻으려면(Unix 시스템에서 유용할 수 있음) 경로에 bytes()를 호출하십시오. 이 메서드는 내부적으로 os.fsencode()를 사용합니다.:

>>> bytes(p)
b'/home/antoine/pathlib/setup.py'

경로를 file: URI로 표현하려면 as_uri() 메서드를 호출하십시오.:

>>> p = PurePosixPath('/etc/passwd')
>>> p.as_uri()
'file:///etc/passwd'
>>> p = PureWindowsPath('c:/Windows')
>>> p.as_uri()
'file:///c:/Windows'

경로의 repr()은 가독성을 위해, 그리고 순방향 슬래시를 사용해도 된다는 점을 사용자에게 상기시키기 위해 Windows에서도 항상 순방향 슬래시를 사용합니다.:

>>> p = PureWindowsPath('c:/Windows')
>>> p
PureWindowsPath('c:/Windows')

속성

모든 경로에는 몇 가지 간단한 속성이 제공됩니다(각 속성은 비어 있을 수 있습니다).:

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.drive
'c:'
>>> p.root
'\\'
>>> p.anchor
'c:\\'
>>> p.name
'pathlib.tar.gz'
>>> p.stem
'pathlib.tar'
>>> p.suffix
'.gz'
>>> p.suffixes
['.tar', '.gz']

새 경로 파생

결합

/ 연산자를 사용하여 한 경로를 다른 경로와 결합할 수 있습니다.:

>>> p = PurePosixPath('foo')
>>> p / 'bar'
PurePosixPath('foo/bar')
>>> p / PurePosixPath('bar')
PurePosixPath('foo/bar')
>>> 'bar' / p
PurePosixPath('bar/foo')

생성자와 마찬가지로 여러 경로 구성 요소를 한꺼번에 지정하거나 개별적으로 지정할 수 있습니다.:

>>> p / 'bar/xyzzy'
PurePosixPath('foo/bar/xyzzy')
>>> p / 'bar' / 'xyzzy'
PurePosixPath('foo/bar/xyzzy')

동일하게 동작하는 joinpath() 메서드도 제공됩니다.:

>>> p.joinpath('Python')
PurePosixPath('foo/Python')

경로의 마지막 구성 요소 변경

with_name() 메서드는 이름이 변경된 새 경로를 반환합니다.:

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_name('setup.py')
PureWindowsPath('c:/Downloads/setup.py')

경로에 실제 이름이 없으면 ValueError가 발생합니다.:

>>> p = PureWindowsPath('c:/')
>>> p.with_name('setup.py')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pathlib.py", line 875, in with_name
    raise ValueError("%r has an empty name" % (self,))
ValueError: PureWindowsPath('c:/') has an empty name
>>> p.name
''

with_suffix() 메서드는 접미사가 변경된 새 경로를 반환합니다. 그러나 경로에 접미사가 없으면 새 접미사가 추가됩니다.:

>>> p = PureWindowsPath('c:/Downloads/pathlib.tar.gz')
>>> p.with_suffix('.bz2')
PureWindowsPath('c:/Downloads/pathlib.tar.bz2')
>>> p = PureWindowsPath('README')
>>> p.with_suffix('.bz2')
PureWindowsPath('README.bz2')

경로를 상대 경로로 만들기

relative_to() 메서드는 한 경로와 다른 경로 사이의 상대적 차이를 계산합니다.:

>>> PurePosixPath('/usr/bin/python').relative_to('/usr')
PurePosixPath('bin/python')

메서드가 의미 있는 값을 반환할 수 없으면 ValueError가 발생합니다.:

>>> PurePosixPath('/usr/bin/python').relative_to('/etc')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pathlib.py", line 926, in relative_to
    .format(str(self), str(formatted)))
ValueError: '/usr/bin/python' does not start with '/etc'

시퀀스와 유사한 접근

parts 프로퍼티는 경로 구성 요소에 읽기 전용 시퀀스 접근을 제공하는 튜플을 반환합니다.:

>>> p = PurePosixPath('/etc/init.d')
>>> p.parts
('/', 'etc', 'init.d')

Windows 경로는 드라이브와 루트를 하나의 경로 구성 요소로 처리합니다.:

>>> p = PureWindowsPath('c:/setup.py')
>>> p.parts
('c:\\', 'setup.py')

(이들을 분리하면 잘못됩니다. C:C:\\의 부모가 아니기 때문입니다.)

parent 프로퍼티는 경로의 논리적 부모를 반환합니다.:

>>> p = PureWindowsPath('c:/python33/bin/python.exe')
>>> p.parent
PureWindowsPath('c:/python33/bin')

parents 프로퍼티는 경로의 논리적 조상에 대한 변경 불가능한 시퀀스를 반환합니다.:

>>> p = PureWindowsPath('c:/python33/bin/python.exe')
>>> len(p.parents)
3
>>> p.parents[0]
PureWindowsPath('c:/python33/bin')
>>> p.parents[1]
PureWindowsPath('c:/python33')
>>> p.parents[2]
PureWindowsPath('c:/')

조회

is_relative()는 경로가 상대 경로이면 True를 반환하고(위의 정의 참조), 그렇지 않으면 False를 반환합니다.

is_reserved()는 Windows 경로가 CON이나 NUL과 같은 예약 경로이면 True를 반환합니다. POSIX 경로에 대해서는 항상 False를 반환합니다.

match()는 경로를 글롭 패턴과 일치시킵니다. 개별 구성 요소에 대해 작동하며 오른쪽부터 일치시킵니다.

>>> p = PurePosixPath('/usr/bin')
>>> p.match('/usr/b*')
True
>>> p.match('usr/b*')
True
>>> p.match('b*')
True
>>> p.match('/u*')
False

이 동작은 다음과 같은 예상 사항을 충족합니다.

  • “*.py”와 같은 단순한 패턴은 마지막 구성 요소가 일치하는 한 임의로 긴 경로와 일치합니다. 예를 들어 “/usr/foo/bar.py”와 같습니다.
  • 더 복잡한 일치를 위해 더 긴 패턴도 사용할 수 있습니다. 예를 들어 “/usr/foo/*.py”는 “/usr/foo/bar.py”와 일치합니다.

구체 경로 API

순수 API의 연산 외에도 구체 경로는 정보를 조회하거나 변경하기 위해 실제로 파일 시스템에 접근하는 추가 메서드를 제공합니다.

생성

클래스 메서드 cwd()는 현재 작업 디렉터리를 가리키는 경로 객체를 절대 형식으로 생성합니다.:

>>> Path.cwd()
PosixPath('/home/antoine/pathlib')

파일 메타데이터

stat()는 파일의 stat() 결과를 반환합니다. 마찬가지로 lstat()는 파일의 lstat() 결과를 반환합니다(파일이 심볼릭 링크인 경우에만 서로 다릅니다).:

>>> p.stat()
posix.stat_result(st_mode=33277, st_ino=7483155, st_dev=2053, st_nlink=1, st_uid=500, st_gid=500, st_size=928, st_atime=1343597970, st_mtime=1328287308, st_ctime=1343597964)

더 높은 수준의 메서드는 파일의 종류를 확인하는 데 도움을 줍니다.:

>>> p.exists()
True
>>> p.is_file()
True
>>> p.is_dir()
False
>>> p.is_symlink()
False
>>> p.is_socket()
False
>>> p.is_fifo()
False
>>> p.is_block_device()
False
>>> p.is_char_device()
False

파일 소유자와 그룹의 이름(숫자 ID가 아님)은 해당 메서드를 통해 조회합니다.:

>>> p = Path('/etc/shadow')
>>> p.owner()
'root'
>>> p.group()
'shadow'

경로 해석

resolve() 메서드는 경로를 절대 경로로 만들며, 그 과정에서 모든 심볼릭 링크를 해석합니다(POSIX의 realpath() 호출과 유사합니다). “..” 경로 구성 요소를 제거하는 유일한 연산입니다. Windows에서는 이 메서드가 (대소문자가 올바른) 정규 경로를 반환하도록 처리하기도 합니다.

디렉터리 순회

단순한(비재귀적) 디렉터리 접근은 iterdir() 메서드를 호출하여 이루어지며, 이 메서드는 자식 경로들에 대한 이터레이터를 반환합니다:

>>> p = Path('docs')
>>> for child in p.iterdir(): child
...
PosixPath('docs/conf.py')
PosixPath('docs/_templates')
PosixPath('docs/make.bat')
PosixPath('docs/index.rst')
PosixPath('docs/_build')
PosixPath('docs/_static')
PosixPath('docs/Makefile')

이를 통해 리스트 컴프리헨션으로 간단히 필터링할 수 있습니다:

>>> p = Path('.')
>>> [child for child in p.iterdir() if child.is_dir()]
[PosixPath('.hg'), PosixPath('docs'), PosixPath('dist'), PosixPath('__pycache__'), PosixPath('build')]

단순 및 재귀적 글로빙도 제공됩니다:

>>> for child in p.glob('**/*.py'): child
...
PosixPath('test_pathlib.py')
PosixPath('setup.py')
PosixPath('pathlib.py')
PosixPath('docs/conf.py')
PosixPath('build/lib/pathlib.py')

파일 열기

open() 메서드는 내장 open() 메서드와 유사한 파일 열기 API를 제공합니다:

>>> p = Path('setup.py')
>>> with p.open() as f: f.readline()
...
'#!/usr/bin/env python3\n'

파일 시스템 수정

여러 일반적인 파일 시스템 연산이 메서드로 제공됩니다: touch(), mkdir(), rename(), replace(), unlink(), rmdir(), chmod(), lchmod(), symlink_to(). 예를 들어 shutil 모듈의 일부 기능과 같이, 더 많은 연산이 제공될 수 있습니다.

제안된 API에 대한 상세한 문서는 pathlib docs에서 확인할 수 있습니다.

논의

나눗셈 연산자

경로 결합 연산자에 관한 poll에서 나눗셈 연산자가 1위를 차지했습니다. pathlib의 초기 버전에서는 대신 대괄호(즉 __getitem__)를 사용했습니다.

joinpath()

joinpath() 메서드는 초기에 join()이라고 불렸지만, 여러 사람이 의미가 다른 str.join()과 혼동될 수 있다고 반대했습니다. 따라서 joinpath()로 이름이 바뀌었습니다.

대소문자 구분

Windows 사용자는 파일 시스템 경로가 대소문자를 구분하지 않는다고 여기며, 드물게 Windows에서 외부 파일 시스템 마운트가 대소문자를 구분하는 경우가 있더라도 경로 객체가 그 특성을 따르기를 기대합니다.

어느 논평자의 말을 빌리면,

“glob(”*.py”)가 Windows에서 SETUP.PY를 찾지 못한다면, 그것은 사용성 재앙이 될 것”입니다.

—Paul Moore, https://mail.python.org/pipermail/python-dev/2013-April/125254.html 에서