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

Python 개선 제안 한국어 번역

PEP 204 – 범위 리터럴

Author:
Thomas Wouters <thomas at python.org>
Status:
Rejected
Type:
Standards Track
Created:
14-Jul-2000
Python-Version:
2.0
Post-History:


Table of Contents

번역·라이선스 안내

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

Warning

This PEP has been rejected.

×

신중한 고려와 숙고의 기간을 거쳐, 이 제안은 거부되었습니다. 범위와 슬라이스 구문 사이의 일부 혼동뿐만 아니라 해결되지 않은 쟁점들로 인해 귀도가 Python 2.0에서 이를 수용하지 않을 만큼 충분한 의문이 제기되었으며, 이후 이 제안은 완전히 거부되었습니다. 새로운 구문과 그 의도가 충분히 명확하지 않다고 판단되었습니다.

[ TBD: 귀도, 이 부분을 수정/확인해 주십시오. 가급적 둘 다 해 주십시오. 이것은 PEP이므로, 향후 참고를 위해 거부 및/또는 재고의 모든 이유를 담고 있어야 합니다. ]

소개

이 PEP는 Python 2.0을 위한 “범위 리터럴” 제안을 설명합니다. 이 PEP는 Python 2.0에 도입될 예정인 이 기능의 상태와 소유권을 추적합니다. 이 문서는 해당 기능에 대한 설명을 담고 있으며, 이 기능을 지원하는 데 필요한 변경 사항을 개괄적으로 설명합니다. 이 PEP는 메일링 리스트 포럼에서 이루어진 논의를 요약하며, 적절한 경우 추가 정보를 위한 URL을 제공합니다. 이 파일의 CVS 리비전 기록에는 확정적인 역사적 기록이 담겨 있습니다.

리스트 범위

범위란 고정된 간격을 가진 숫자들의 시퀀스로, for-루프에서 자주 사용됩니다. Python의 for-루프는 시퀀스를 직접 순회하도록 설계되었습니다.:

>>> l = ['a', 'b', 'c', 'd']
>>> for item in l:
...     print item
a
b
c
d

그러나 이 해법이 항상 신중한 것은 아닙니다. 첫째, for-루프의 본문에서 시퀀스를 변경할 때 문제가 발생하며, 그 결과 for-루프가 항목을 건너뛰게 됩니다. 둘째, 예를 들어 시퀀스의 매 두 번째 요소만을 순회하는 것은 불가능합니다. 그리고 셋째, 때로는 인덱스를 기준으로 요소를 처리해야 할 필요가 있는데, 위의 구성에서는 인덱스를 쉽게 사용할 수 없습니다.

이러한 경우와, 숫자 범위가 필요한 그 밖의 경우를 위해, Python은 숫자 리스트를 생성하는 range 내장 함수를 제공합니다. range 함수는 세 개의 인자, startendstep을 받습니다. startstep은 선택적이며, 기본값은 각각 0과 1입니다.

range 함수는 start에서 시작하여 step만큼의 간격으로 end를 포함하지 않을 때까지 숫자 리스트를 생성하며, 따라서 range(10)은 정확히 10개의 항목, 즉 0부터 9까지의 숫자를 가진 리스트를 생성합니다.

range 함수를 사용하면, 위의 예시는 다음과 같은 모습이 됩니다.:

>>> for i in range(len(l)):
...     print l[i]
a
b
c
d

또는, l의 두 번째 요소부터 시작하여 그 이후로 매 두 번째 요소만을 처리하려면 다음과 같습니다.:

>>> for i in range(1, len(l), 2):
...     print l[i]
b
d

이 접근 방식에는 몇 가지 단점이 있습니다.

  • 목적의 명확성: 원하는 리스트의 길이와 간격을 결정하기 위한 추가적인 산술 연산이 수반될 수 있는 또 다른 함수 호출을 추가하는 것은 코드의 가독성을 개선하지 않습니다. 또한, 같은 이름의 지역 또는 전역 변수를 지정함으로써 내장 range 함수를 사실상 대체하는, 즉 “가리는” 것이 가능합니다. 이는 의도된 효과일 수도 있고 아닐 수도 있습니다.
  • 효율성: range 함수가 재정의될 수 있기 때문에, Python 컴파일러는 for-루프에 대해 가정을 할 수 없으며, 별도의 루프 카운터를 유지해야 합니다.
  • 일관성: 아래에 보이듯이, 범위를 나타내는 데 사용되는 구문이 이미 존재합니다. 이 구문은 모두 선택 사항이기는 하지만, 정확히 동일한 방식으로 정확히 동일한 인자를 사용합니다. 이 구문을 범위로 확장하여 “범위 리터럴”을 형성하는 것이 논리적으로 타당해 보입니다.

슬라이스 인덱스

파이썬에서 시퀀스는 두 가지 방법 중 하나로 인덱싱할 수 있습니다: 단일 항목을 가져오거나, 항목의 범위를 가져오는 것입니다. 항목의 범위를 가져오면 원본 시퀀스와 같은 타입의 새 객체가 생성되며, 이 객체는 원본 시퀀스의 항목을 0개 이상 포함합니다. 이는 “범위 표기법”을 사용하여 수행됩니다:

>>> l[2:4]
['c', 'd']

이 범위 표기법은 콜론으로 구분된 0개, 1개 또는 2개의 인덱스로 구성됩니다. 첫 번째 인덱스는 start 인덱스이고, 두 번째는 end입니다. 둘 중 하나가 생략되면, 각각 시퀀스의 시작과 끝으로 기본 설정됩니다.

step까지 포함하는 확장된 범위 표기법도 있습니다. 이 표기법은 현재 대부분의 내장 타입에서 지원되지 않지만, 지원된다면 다음과 같이 동작할 것입니다:

>>> l[1:4:2]
['b', 'd']

슬라이스 구문의 세 번째 “인자”는 range()step 인자와 정확히 동일합니다. 표준 슬라이스와 이 확장된 슬라이스의 기저 메커니즘은 충분히 다르고 일관성이 없어서, 수학 패키지 외부의 많은 클래스와 확장 기능은 확장된 변형에 대한 지원을 구현하지 않습니다. 이는 해결되어야 하지만, 이 PEP의 범위를 벗어납니다.

하지만 확장된 슬라이스는, 앞서 언급한 range() 함수 사용의 단점을 모두 해결하는 방식으로 범위를 나타내는 완전히 유효하고 적용 가능한 구문이 이미 존재함을 보여줍니다:

  • 더 명확하고 간결한 구문이며, 이미 직관적이고 배우기 쉬운 것으로 입증되었습니다.
  • 파이썬에서 범위의 다른 용도(예: 슬라이스)와 일관성이 있습니다.
  • 내장 함수 대신 내장 구문이기 때문에, 오버라이드할 수 없습니다. 이는 코드를 보는 사람이 코드가 무엇을 하는지 확신할 수 있고, 최적화기가 range()가 “가려지는” 것에 대해 걱정할 필요가 없음을 의미합니다.

제안된 해결책

제안된 범위 리터럴 구현은 리스트 리터럴 구문과 (확장된) 슬라이스 구문을 결합하여 범위 리터럴을 형성합니다:

>>> [1:10]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [:5]
[0, 1, 2, 3, 4]
>>> [5:1:-1]
[5, 4, 3, 2]

범위 리터럴과 슬라이스 구문 사이에는 한 가지 사소한 차이가 있습니다: 슬라이스에서는 start, end, step을 모두 생략할 수 있지만, 범위 리터럴에서는 end를 생략하는 것이 의미가 없습니다. 슬라이스에서는 end가 기본적으로 리스트의 끝으로 설정되지만, 범위 리터럴에서는 이것이 아무 의미가 없습니다.

참조 구현

제안된 구현은 SourceForge [1]에서 찾을 수 있습니다. 이는 스택에서 세 개의 인자를 받아 이를 기반으로 리스트를 만드는 새로운 바이트코드 BUILD_RANGE를 추가합니다. 그 리스트는 다시 스택에 푸시됩니다.

컴파일 시점에는 결과를 알 수 없는 다른 계산을 기반으로 범위를 만들 수 있으려면 새로운 바이트코드의 사용이 필요합니다.

이 코드는 listobject.c에 두 개의 새로운 함수를 도입하는데, 이 함수들은 현재 비공개 함수와 완전한 API 호출 사이에 걸쳐 있습니다.

PyList_FromRange()는 start, end, step으로부터 리스트를 만들며, 오류가 발생하면 NULL을 반환합니다. 그 프로토타입은 다음과 같습니다:

PyObject * PyList_FromRange(long start, long end, long step)

PyList_GetLenOfRange()는 범위(range)의 길이를 결정하는 데 사용되는 헬퍼 함수입니다. 이전에는 bltinmodule.c의 정적 함수였지만, 이제는 listobject.cbltinmodule.c모두에서(xrange를 위해) 필요합니다. 코드 중복을 피하기 위한 목적만으로 비정적(non-static) 함수로 만들었습니다. 그 프로토타입은 다음과 같습니다.:

long PyList_GetLenOfRange(long start, long end, long step)

미해결 쟁점

  • 범위 리터럴에서 end 인자를 요구하는 것의 불일치에 대한 한 가지 가능한 해결책은, xrange 내장 함수가 하는 것처럼 범위 구문이 리스트가 아니라 “제너레이터”를 생성하도록 허용하는 것입니다. 하지만 제너레이터는 리스트가 아니므로, 예를 들어 제너레이터의 항목에 값을 대입하거나 항목을 추가(append)하는 것이 불가능할 것입니다.

    범위 구문은 튜플(즉, 불변 리스트)을 포함하도록 확장될 수 있을 것으로 생각되며, 이 경우 제너레이터로 안전하게 구현될 수 있을 것입니다. 이는 특히 큰 숫자 배열에 바람직한 해결책일 수 있습니다: 제너레이터는 저장과 초기화 측면에서 요구되는 것이 매우 적으며, 요청 시 적절한 숫자를 계산하고 생성하는 데 드는 성능상의 영향도 작습니다. (TBD: 애초에 그런 영향이 있기는 한 것인지? 간단한 테스트로는 길이가 1인 범위의 경우에도 동일한 성능을 보이는 것으로 나타났습니다)

    하지만 설령 이 아이디어가 채택되더라도, 구문의 한 형태에서는 두 번째 인자를 선택적으로 만들고 다른 경우에는 필수로 만드는 식으로 “특별 취급”하는 것이 현명할지는 의문입니다.

  • 범위 구문을 일반 리스트 리터럴과 혼합하여 하나의 리스트를 생성하는 것이 가능해야 할까요? 예를 들어:
    >>> [5, 6, 1:6, 7, 9]
    

    다음을 생성하기 위해:

    [5, 6, 1, 2, 3, 4, 5, 7, 9]
    
  • range 리터럴이 제안된 또 다른 새 기능인 “list comprehensions”와 어떻게 상호작용해야 합니까? 구체적으로, 리스트 컴프리헨션 안에서 리스트를 만들 수 있어야 합니까? 예를 들면::
    >>> [x:y for x in (1, 2) y in (3, 4)]
    

    이 예제는 여러 range를 가진 단일 리스트를 반환해야 합니까:

    [1, 2, 1, 2, 3, 2, 2, 3]
    

    아니면 다음과 같이 리스트의 리스트를 반환해야 합니까:

    [[1, 2], [1, 2, 3], [2], [2, 3]]
    

    하지만 리스트 컴프리헨션의 구문과 의미론은 여전히 뜨거운 논쟁의 대상이므로, 이 문제들은 아마도 “list comprehensions” PEP에서 다루는 것이 가장 좋을 것입니다.

  • range 리터럴은 정수 이외의 객체도 받아들입니다: 전달된 객체에 대해 PyInt_AsLong()을 수행하므로, 객체가 정수로 강제 변환될 수 있는 한 받아들여집니다. 하지만 결과로 만들어지는 리스트는 항상 표준 정수로 구성됩니다.

    range 리터럴은 전달된 타입의 리스트를 만들어야 합니까? long이나 문자열 같은 다른 내장 타입의 경우에는 바람직할 수 있습니다:

    >>> [ 1L : 2L<<64 : 2<<32L ]
    >>> ["a":"z":"b"]
    >>> ["a":"z":2]
    

    하지만 이는 명백하다고 보기에는 지나친 “마법”일 수 있습니다. 또한 사용자 정의 클래스에서 문제를 일으킬 수도 있습니다: 베이스 클래스를 찾아 새 인스턴스를 생성할 수 있다 하더라도, 그 인스턴스는 __init__에 추가 인자가 필요할 수 있어 생성이 실패하게 될 수 있습니다.

  • PyList_FromRange()PyList_GetLenOfRange() 함수는 분류가 필요합니다: 이들은 API의 일부입니까, 아니면 비공개(private) 함수로 만들어야 합니까?

References