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

Python 개선 제안 한국어 번역

PEP 472 – 키워드 인자를 사용한 인덱싱 지원

Author:
Stefano Borini, Joseph Martinot-Lagarde
Discussions-To:
Python-Ideas list
Status:
Rejected
Type:
Standards Track
Created:
24-Jun-2014
Python-Version:
3.6
Post-History:
02-Jul-2014
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 키워드 인자를 지원하도록 인덱싱 연산을 확장할 것을 제안합니다. a[K=3,R=2] 형태의 표기법이 올바른 문법이 됩니다. 향후 확장성을 고려하여 a[1:2, K=3, R=4]도 검토하며, 구현 방식의 선택에 따라 이 역시 허용될 수 있습니다. 파서 변경과 더불어 인덱스 프로토콜(__getitem__, __setitem__ 및 __delitem__)도 조정이 필요할 수 있습니다.

동기

인덱싱 문법은 강한 의미적 내용을 담고 있어 메서드 호출과 구별됩니다. 인덱싱 문법은 데이터의 부분 집합을 참조한다는 것을 암시합니다. 저희는 이러한 의미론적 연관이 중요하다고 믿으며, 이 데이터를 참조하는 데 허용되는 방식을 확장하고자 합니다.

일반적으로 인덱싱 연산에 필요한 인덱스의 개수는 데이터의 차원에 따라 달라집니다. 1차원 데이터(예: 리스트)에는 인덱스 하나(예: a[3])가 필요하고, 2차원 데이터(예: 행렬)에는 인덱스 두 개(예: a[2,3])가 필요한 식입니다. 각 인덱스는 차원의 축 중 하나를 따라 선택하는 선택자이며, 인덱스 튜플 내의 위치는 각 인덱스를 해당 축에 연결하는 데 필요한 메타 정보입니다.

현재 Python 문법은 축과의 연관을 표현하는 데 오로지 위치에만 의존하며, 점이 아닌 선택(슬라이스)을 참조하기 위한 문법적 설탕도 포함하고 있습니다

>>> a[3]       # returns the fourth element of a
>>> a[1:10:2]  # slice notation (extract a non-trivial data subset)
>>> a[3,2]     # multiple indexes (for multidimensional arrays)

이 PEP에서 제안하는 추가 표기법을 사용하면 인덱싱 연산에서 키워드 인자를 포함하는 표기법을 쓸 수 있습니다. 예를 들면 다음과 같습니다.

>>> a[K=3, R=2]

이를 통해 관례적인 이름으로 축을 참조할 수 있습니다.

이에 더해 위치와 키워드 명세를 모두 허용하는 확장된 형태도 고려해야 합니다.

>>> a[3,R=3,K=4]

이 PEP에서는 이러한 표기법을 사용할 수 있게 하는 여러 가지 방식을 살펴봅니다.

사용 사례

다음의 실용적인 사용 사례는 키워드 명세의 사용을 크게 두 가지 범주로 보여 줍니다. 인덱싱과 문맥적 옵션입니다. 인덱싱의 경우는 다음과 같습니다.

  1. 인덱스에 더 명확한 의미를 부여하여, 예를 들어 인덱스가 실수로 뒤바뀌는 것을 방지합니다.
    >>> gridValues[x=3, y=5, z=8]
    >>> rain[time=0:12, location=location]
    
  2. 계산 물리학이나 계산 화학 같은 일부 분야에서는 Basis[Z=5]와 같은 표기법이 정확도 수준을 나타내는 도메인 특화 언어 표기법으로 쓰입니다.
    >>> low_accuracy_energy = computeEnergy(molecule, BasisSet[Z=3])
    

    이 경우 인덱스 연산은 선택한 정확도 수준(매개변수 Z로 표현됨)의 기저 집합을 반환합니다. 인덱싱을 사용하는 이유는 BasisSet 객체가 내부적으로 숫자 테이블로 표현될 수 있기 때문입니다. 이 테이블에서 행(이 예에서는 사용자에게 드러나지 않는 “계수” 축)은 개별 원소에 연결되고(예: 0:5행에는 원소 1의 계수가, 5:8행에는 원소 2의 계수가 들어 있음), 각 열은 특정 정확도 수준(“정확도” 또는 “Z” 축)에 연결되어 첫 번째 열은 낮은 정확도, 두 번째 열은 중간 정확도와 같은 식으로 구성됩니다. 이러한 인덱싱을 통해 사용자는 정확도 수준 3에 해당하는 내부 테이블 열의 내용을 나타내는 또 다른 객체를 얻게 됩니다.

또한 키워드 명세는 인덱싱에 대한 문맥적 옵션으로 사용될 수 있습니다. 구체적으로는 다음과 같습니다.

  1. “default” 옵션을 사용하면 인덱스가 존재하지 않을 때의 기본 반환값을 지정할 수 있습니다
    >>> lst = [1, 2, 3]
    >>> value = lst[5, default=0]  # value is 0
    
  2. 희소 데이터셋의 경우, 예를 들어 주변 데이터로부터 누락된 지점을 추론하는 보간 전략을 지정할 수 있습니다.
    >>> value = array[1, 3, interpolate=spline_interpolator]
    
  3. 같은 메커니즘으로 단위를 지정할 수도 있습니다.
    >>> value = array[1, 3, unit="degrees"]
    

표기법을 어떻게 해석할지는 구현하는 클래스에 달려 있습니다.

현재 구현

현재 인덱싱 연산은 __getitem__, __setitem__, __delitem__ 메서드가 처리합니다. 이 메서드들의 시그니처는 인덱스를 위한 인자 하나를 받습니다(__setitem__은 설정할 값을 위한 인자를 하나 더 받습니다). 이하에서는 __getitem__(self, idx)만 분석하며, 나머지 두 메서드에도 같은 고려 사항이 적용되는 것으로 간주합니다.

인덱싱 연산이 수행되면 __getitem__(self, idx)가 호출됩니다. 전통적으로 대괄호 사이의 전체 내용은 하나의 객체로 변환되어 idx 인자에 전달됩니다:

  • 요소 하나가 전달되면(예: a[2]) idx는 2가 됩니다.
  • 여러 요소를 전달할 때는 쉼표로 구분해야 합니다: a[2, 3]. 이 경우 idx는 튜플 (2, 3)이 됩니다. a[2, 3, "hello", {}]에서는 idx가 (2, 3, "hello", {})가 됩니다.
  • 예를 들어 a[2:10]과 같은 슬라이싱 표기법은 슬라이스 객체를 생성하며, 여러 값이 전달된 경우에는 슬라이스 객체를 포함하는 튜플을 생성합니다.

슬라이스 표기법을 처리하는 고유한 능력을 제외하면, 인덱싱 연산은 일반 메서드 호출과 유사점이 있습니다. 요소 하나만으로 호출될 때는 메서드 호출처럼 동작하며, 요소의 수가 하나보다 많으면 idx 인자는 *args처럼 동작합니다. 그러나 동기 절에서 언급했듯이, 인덱싱 연산에는 더 큰 집합에서 부분 집합을 추출한다는 강한 의미가 담겨 있으며, 일반 메서드 호출은 적절한 이름을 선택하지 않는 한 이러한 의미와 자동으로 연결되지 않습니다. 게다가 인덱싱 연산이 시각적으로 구별되는 형태를 띤다는 점은 가독성에 중요합니다.

명세

구현은 __getitem__의 현재 시그니처를 유지하거나, 하위 호환성을 지키는 방식으로 수정하도록 해야 합니다. 다뤄야 할 수 있는 경우들을 고려하여 여러 대안을 제시하겠습니다

C0. a[1]; a[1,2]         # Traditional indexing
C1. a[Z=3]
C2. a[Z=3, R=4]
C3. a[1, Z=3]
C4. a[1, Z=3, R=4]
C5. a[1, 2, Z=3]
C6. a[1, 2, Z=3, R=4]
C7. a[1, Z=3, 2, R=4]    # Interposed ordering

“엄격한 딕셔너리” 전략

이 전략은 __getitem__이 객체를 하나만 받는다는 점에서 특별하며, 그 객체의 성격은 축의 명세에 있어 모호하지 않아야 한다는 점을 인정합니다. 즉, 순서로 지정하거나 이름으로 지정하는 둘 중 하나여야 합니다. 이 가정에 따라 키워드 인자가 있으면 전달되는 대상은 딕셔너리이며, 모든 레이블을 지정해야 합니다.

C0. a[1]; a[1,2]      -> idx = 1; idx = (1, 2)
C1. a[Z=3]            -> idx = {"Z": 3}
C2. a[Z=3, R=4]       -> idx = {"Z": 3, "R": 4}
C3. a[1, Z=3]         -> raise SyntaxError
C4. a[1, Z=3, R=4]    -> raise SyntaxError
C5. a[1, 2, Z=3]      -> raise SyntaxError
C6. a[1, 2, Z=3, R=4] -> raise SyntaxError
C7. a[1, Z=3, 2, R=4] -> raise SyntaxError

장점

  • 튜플의 경우와 딕셔너리의 경우가 개념적으로 매우 유사합니다. 첫 번째 경우에는 튜플을 지정하므로, 자연스럽게 쉼표로 구분된 단순한 값의 집합을 정의하게 됩니다. 두 번째 경우에는 딕셔너리를 지정하므로, dict(Z=3, R=4)에서처럼 동질적인 키/값 쌍의 집합을 지정하게 됩니다;
  • __getitem__ 쪽에서 파싱하기가 간단하고 쉽습니다. 튜플을 받으면 위치를 사용해 축을 결정합니다. 딕셔너리를 받으면 키워드를 사용합니다.
  • C 인터페이스를 변경할 필요가 없습니다.

중립

  • a[{"Z": 3, "R": 4}]와 a[Z=3, R=4]가 중복된다는 것은 이 표기법이 문법적 설탕이라는 것을 의미합니다.

단점

  • 매우 엄격합니다.
  • 전달된 인자의 순서가 사라집니다. PEP 468에서 초안으로 제시한 대로 OrderedDict를 사용하면 순서를 유지할 수 있습니다.
  • a[1, 2, default=5]와 같이 위치 인자와 키워드 인자가 섞인 사용 사례를 허용하지 않습니다.

“혼합 딕셔너리” 전략

이 전략은 위의 제약을 완화하여 숫자와 문자열을 모두 키로 포함하는 딕셔너리를 반환합니다.

C0. a[1]; a[1,2]      -> idx = 1; idx = (1, 2)
C1. a[Z=3]            -> idx = {"Z": 3}
C2. a[Z=3, R=4]       -> idx = {"Z": 3, "R": 4}
C3. a[1, Z=3]         -> idx = { 0: 1, "Z": 3}
C4. a[1, Z=3, R=4]    -> idx = { 0: 1, "Z": 3, "R": 4}
C5. a[1, 2, Z=3]      -> idx = { 0: 1, 1: 2, "Z": 3}
C6. a[1, 2, Z=3, R=4] -> idx = { 0: 1, 1: 2, "Z": 3, "R": 4}
C7. a[1, Z=3, 2, R=4] -> idx = { 0: 1, "Z": 3, 2: 2, "R": 4}

장점

  • 혼합된 경우를 허용합니다.

단점

  • 문자열 키의 순서 정보가 사라집니다. C7의 "Z"가 1번 위치에 있었는지 3번 위치에 있었는지 알 방법이 없습니다.
  • 지정된 인덱스 중 하나라도 키워드 인자를 가지면 즉시 튜플에서 딕셔너리로 바뀐다는 뜻입니다. 파싱하기에 혼란스러울 수 있습니다.

“네임드 튜플” 전략

idx에 튜플 대신 네임드 튜플을 반환합니다. 키워드 인자는 당연히 명시된 이름을 키로 가지며, 위치 인자는 밑줄 뒤에 순서 번호가 붙은 이름을 갖습니다:

C0. a[1]; a[1,2]      -> idx = 1; idx = (_0=1, _1=2)
C1. a[Z=3]            -> idx = (Z=3)
C2. a[Z=3, R=2]       -> idx = (Z=3, R=2)
C3. a[1, Z=3]         -> idx = (_0=1, Z=3)
C4. a[1, Z=3, R=2]    -> idx = (_0=1, Z=3, R=2)
C5. a[1, 2, Z=3]      -> idx = (_0=1, _2=2, Z=3)
C6. a[1, 2, Z=3, R=4] -> (_0=1, _1=2, Z=3, R=4)
C7. a[1, Z=3, 2, R=4] -> (_0=1, Z=3, _1=2, R=4)
                      or (_0=1, Z=3, _2=2, R=4)
                      or raise SyntaxError

namedtuple에 필요한 타입 이름은 Index 또는 함수 정의에 있는 인자의 이름이 될 수 있습니다. 이 방식은 순서를 유지하며 _fields 속성을 사용하여 쉽게 분석할 수 있습니다. 항목이 둘 이상인 C0이 이제 일반 튜플 대신 namedtuple을 전달한다는 점을 제외하면 하위 호환성이 있습니다.

장점

  • 보기 좋습니다. namedtuple은 튜플을 투명하게 대체하며 기존 동작으로 자연스럽게 후퇴합니다.
  • C 인터페이스를 변경할 필요가 없습니다

단점

  • 일부 자료에 따르면 namedtuple은 충분히 발전하지 않았습니다. 이를 이처럼 중요한 객체로 포함하려면 아마도 재작업과 개선이 필요할 것입니다;
  • namedtuple의 필드와 그에 따른 타입이 전달된 인자에 따라 바뀌어야 합니다. 이는 성능 병목이 될 수 있으며, 연속된 두 인덱스 접근이 같은 Index 클래스를 얻는다고 보장할 수 없게 만듭니다;
  • _n “매직” 필드는 다소 특이하지만, ipython은 이미 결과 기록에 이를 사용하고 있습니다.
  • Python에는 현재 내장 namedtuple이 없습니다. 현재의 namedtuple은 표준 라이브러리의 “collections” 모듈에서 사용할 수 있습니다.
  • 함수와 달리, 호출 시점에 순서가 바뀌면(예를 들어 gridValues[y=5, z=8, x=3])를 요청하는 경우) gridValues[x=3, y=5, z=8]과 gridValues[3,5,8] 두 표기법은 매끄럽게 대응되지 않습니다. 함수에서는 인자 이름을 미리 정의해 두어 키워드 인자가 올바르게 대응되도록 할 수 있습니다. __getitem__에서는 그렇지 않으므로, 해석하고 대응시키는 작업은 __getitem__ 자체에 맡겨집니다.

“새로운 인자 내용” 전략

현재 구현에서는 __getitem__에 여러 인자가 전달되면 이들이 하나의 튜플로 묶이고, 이 튜플이 단일 인자 idx로 __getitem__에 전달됩니다. 이 전략은 현재 시그니처를 유지하되, idx의 타입과 내용이 가질 수 있는 변동 범위를 더 복잡한 표현으로 확장합니다.

이 전략을 구현하는 방법으로 다음 네 가지를 생각할 수 있습니다:

  • P1: 키워드 인자에 딕셔너리 하나를 사용합니다.
  • P2: 항목이 하나뿐인 딕셔너리를 각각 따로 사용합니다.
  • P3: P2와 비슷하지만, 항목이 하나뿐인 딕셔너리 대신 (key, value) 튜플을 사용합니다.
  • P4: P2와 비슷하지만, 특별한 새 객체를 추가로 사용합니다: keyword()

이 가운데 일부는 퇴화한 표기법이 됩니다. 즉, 이미 가능한 표현과 구별할 수 없게 됩니다. 이번에도 제안된 표기법은 해당 표현에 대한 문법적 설탕이 됩니다.

이 전략에서는 C0의 기존 동작이 바뀌지 않습니다.

C0: a[1]        -> idx = 1                    # integer
    a[1,2]      -> idx = (1,2)                # tuple

C1에서는 특정 인덱싱 항목의 키와 값 쌍을 나타내는 데 딕셔너리나 튜플 중 하나를 사용할 수 있습니다. C1에서는 튜플 안에 튜플을 넣어야 합니다. 그렇지 않으면 a["Z", 3]과 a[Z=3]을 구별할 수 없기 때문입니다.

C1: a[Z=3]      -> idx = {"Z": 3}             # P1/P2 dictionary with single key
                or idx = (("Z", 3),)          # P3 tuple of tuples
                or idx = keyword("Z", 3)      # P4 keyword object

보시다시피 P1/P2 표기법에서는 a[Z=3]과 a[{"Z": 3}]이 정확히 같은 값을 넘겨 __getitem__을 호출하게 됩니다. 따라서 전자는 후자에 대한 문법적 설탕입니다. P3에서도 인덱스는 다르지만 같은 상황이 발생합니다. P4처럼 키워드 객체를 사용하면 이 퇴화 문제가 사라집니다.

C2의 경우는 다음과 같습니다:

C2. a[Z=3, R=4] -> idx = {"Z": 3, "R": 4}     # P1 dictionary/ordereddict
                or idx = ({"Z": 3}, {"R": 4}) # P2 tuple of two single-key dict
                or idx = (("Z", 3), ("R", 4)) # P3 tuple of tuples
                or idx = (keyword("Z", 3),
                          keyword("R", 4) )   # P4 keyword objects

P1은 전통적인 **kwargs 동작에 자연스럽게 대응되지만, 인덱스에 항목이 두 개 이상 있으면 튜플이 만들어진다는 관례를 깨뜨립니다. P2는 이 동작을 유지하면서 순서도 보존합니다. PEP 468의 초안에 제시된 OrderedDict를 사용해도 순서를 보존할 수 있습니다.

나머지 경우는 다음과 같습니다:

C3. a[1, Z=3]   -> idx = (1, {"Z": 3})                     # P1/P2
                or idx = (1, ("Z", 3))                     # P3
                or idx = (1, keyword("Z", 3))              # P4

C4. a[1, Z=3, R=4] -> idx = (1, {"Z": 3, "R": 4})          # P1
                   or idx = (1, {"Z": 3}, {"R": 4})        # P2
                   or idx = (1, ("Z", 3), ("R", 4))        # P3
                   or idx = (1, keyword("Z", 3),
                                keyword("R", 4))           # P4

C5. a[1, 2, Z=3]   -> idx = (1, 2, {"Z": 3})               # P1/P2
                   or idx = (1, 2, ("Z", 3))               # P3
                   or idx = (1, 2, keyword("Z", 3))        # P4

C6. a[1, 2, Z=3, R=4] -> idx = (1, 2, {"Z":3, "R": 4})     # P1
                      or idx = (1, 2, {"Z": 3}, {"R": 4})  # P2
                      or idx = (1, 2, ("Z", 3), ("R", 4))  # P3
                      or idx = (1, 2, keyword("Z", 3),
                                      keyword("R", 4))     # P4

C7. a[1, Z=3, 2, R=4] -> idx = (1, 2, {"Z": 3, "R": 4})    # P1. Pack the keyword arguments. Ugly.
                      or raise SyntaxError                 # P1. Same behavior as in function calls.
                      or idx = (1, {"Z": 3}, 2, {"R": 4})  # P2
                      or idx =  (1, ("Z", 3), 2, ("R", 4)) # P3
                      or idx =  (1, keyword("Z", 3),
                                 2, keyword("R", 4))       # P4

장점

  • 시그니처가 바뀌지 않습니다.
  • P2/P3은 인덱싱할 때 지정한 키워드 인자의 순서를 보존할 수 있습니다.
  • P1에는 OrderedDict가 필요하며, 키워드 인덱스를 다른 인덱스 사이에 섞어 쓰는 것을 허용하면 그 순서가 깨집니다: 모든 키워드 인덱스가 딕셔너리 하나에 몰려 들어가기 때문입니다.
  • 전통적인 타입인 튜플과 딕셔너리의 범위를 벗어나지 않습니다. 경우에 따라서는 OrderedDict도 사용합니다.
  • 제안된 전략 중 일부는 전통적인 함수 호출과 동작 방식이 비슷합니다.
  • PyObject_GetItem 계열의 C 인터페이스는 바뀌지 않습니다.

단점

  • 복잡하고 낭비가 많아 보입니다.
  • 표기법이 퇴화합니다(예: __[get|set|del]item__ 수준에서는 a[Z=3]과 a[{"Z":3}]이 동등하며 구별할 수 없는 표기법입니다). 이 동작은 받아들일 수도 있고 받아들일 수 없을 수도 있습니다.
  • P4의 경우 slice()와 본질적으로 유사한 추가 객체(슬라이스와 비슷한 객체)가 필요하지만, 이는 오직 위의 중복성을 해소하기 위한 것입니다.
  • idx의 타입과 레이아웃이 호출자의 변덕에 따라 바뀌는 것처럼 보입니다.
  • 전달된 것을 파싱하기가 복잡할 수 있으며, 튜플의 튜플인 경우에는 특히 그렇습니다.
  • P2는 튜플의 멤버로 키가 하나뿐인 딕셔너리를 많이 만듭니다. 보기 좋지 않습니다. P3는 딕셔너리의 튜플보다 가볍고 사용하기 쉬우며 (일반 dict와 달리) 순서도 보존하지만, 키워드를 꺼내는 방식이 어색해집니다.

“kwargs 인자” 전략

__getitem__은 키워드 전용이어야 하는 선택적 **kwargs 인자를 받습니다. 키워드가 아닌 인자를 전혀 허용하지 않는 경우를 지원하기 위해 idx도 선택적이 됩니다. 그러면 시그니처는 다음 중 하나가 됩니다.

__getitem__(self, idx)
__getitem__(self, idx, **kwargs)
__getitem__(self, **kwargs)

이를 우리의 사례에 적용하면 다음과 같습니다.

C0. a[1,2]            -> idx=(1,2);  kwargs={}
C1. a[Z=3]            -> idx=None ;  kwargs={"Z":3}
C2. a[Z=3, R=4]       -> idx=None ;  kwargs={"Z":3, "R":4}
C3. a[1, Z=3]         -> idx=1    ;  kwargs={"Z":3}
C4. a[1, Z=3, R=4]    -> idx=1    ;  kwargs={"Z":3, "R":4}
C5. a[1, 2, Z=3]      -> idx=(1,2);  kwargs={"Z":3}
C6. a[1, 2, Z=3, R=4] -> idx=(1,2);  kwargs={"Z":3, "R":4}
C7. a[1, Z=3, 2, R=4] -> raise SyntaxError # in agreement to function behavior

빈 인덱싱 a[]는 물론 여전히 잘못된 문법입니다.

장점

  • 함수 호출과 비슷하며, 함수 호출에서 자연스럽게 확장된 형태입니다.
  • __getitem__에 kwargs가 없는 객체에 키워드 인덱싱을 사용하면 명확한 방식으로 실패합니다. 다른 전략들은 그렇지 않습니다.

단점

  • OrderedDict를 사용하지 않는 한 순서를 보존하지 않습니다.
  • C7을 금지하지만, 그것이 정말 필요합니까?
  • 키워드 인자를 위한 PyObject를 추가로 전달하도록 C 인터페이스를 변경해야 합니다.

C 인터페이스

앞의 분석에서 간략히 소개했듯이, 새 기능을 허용하려면 C 인터페이스를 변경해야 할 수도 있습니다. 구체적으로, “kwargs 인자” 전략의 경우 PyObject_GetItem 및 관련 루틴이 PyObject *kw 인자를 추가로 받아야 합니다. 나머지 전략들은 C 함수 시그니처를 변경할 필요가 없지만, 전달되는 객체의 성격이 달라지므로 그에 맞춘 조정이 필요할 수 있습니다.

“네임드 튜플” 전략은 아무 변경 없이도 올바르게 동작합니다. collections의 팩토리 메서드가 반환하는 클래스는 tuple의 서브클래스이므로, PyTuple_* 함수들이 그 결과 객체를 처리할 수 있습니다.

대안

이 절에서는 누락된 기능을 우회하여 제안된 개선을 구현할 가치가 없게 만드는 대안들을 제시합니다.

메서드 사용

인덱싱은 그대로 두고, 기본 인덱싱으로 충분하지 않은 경우에는 전통적인 get() 메서드를 사용할 수도 있습니다. 타당한 지적이지만, 서론에서 이미 언급했듯이 메서드는 인덱싱과 의미적 무게가 다르며, 메서드에서는 슬라이스를 직접 사용할 수 없습니다. 예를 들어 a[1:3, Z=2]와 a.get(slice(1,3), Z=2)를 비교해 보십시오.

그러나 저자들은 이 인자(논거)가 설득력 있다는 점을 인정하며, 키워드 기반 인덱싱이 주는 의미 표현력의 이점은 충분한 이득을 가져오지 못하고 채택이 제한될 수 있는 드물게 쓰이는 기능이라는 점으로 상쇄될 수 있습니다.

슬라이스 객체를 남용하여 요청된 동작 흉내 내기

이 매우 창의적인 방법은 키로 문자열을 사용하는 것(또는 적절한 이름의 자리 표시자 객체를 인스턴스화하는 것)과 “=” 대신 “:”을 사용하는 것을 받아들인다면, 슬라이스 객체의 동작을 활용합니다.

>>> a["K":3]
slice('K', 3, None)
>>> a["K":3, "R":4]
(slice('K', 3, None), slice('R', 4, None))
>>>

영리한 방식인 것은 분명하지만, 이 접근 방식은 키/값 쌍을 쉽게 조회할 수 없고, 지나치게 기교적이며 난해하고, a[K=1:10:2]처럼 슬라이스를 전달할 수도 없습니다.

그러나 Tim Delaney는 다음과 같이 논평합니다.

“저는 a[b=c, d=e]가 그저 a['b':c, 'd':e]의 문법적 설탕이어야 한다고 정말로 생각합니다. 설명하기 쉽고, 하위 호환성도 가장 좋습니다. 특히, 이미 이런 방식으로 슬라이스를 남용하던 라이브러리들은 새 문법에서도 그대로 계속 동작할 것입니다.”

저희는 이 동작이 불편한 결과를 낳을 것이라고 생각합니다. Pandas 라이브러리는 문자열을 레이블로 사용하므로 다음과 같은 표기법이 가능합니다

>>> a[:, "A":"F"]

이는 “A” 열부터 “F” 열까지의 데이터를 추출합니다. 위 의견에 따르면 이 표기법은 다음과 같이 동일하게 얻을 수 있습니다

>>> a[:, A="F"]

이는 이상할 뿐만 아니라, 인덱싱에서 키워드가 의도하는 의미, 즉 위치가 아니라 관례적인 이름을 통해 축을 지정한다는 의미와 충돌합니다.

딕셔너리를 추가 인덱스로 전달하기

>>> a[1, 2, {"K": 3}]

이 표기법은 덜 우아하긴 하지만 이미 사용할 수 있으며 비슷한 결과를 얻을 수 있습니다. 제안된 “새로운 인자 내용” 전략을 이 표기법에 대한 문법적 설탕으로 해석할 수 있다는 것은 분명합니다.

추가 의견

논평자들은 다음과 같은 관련 논점도 제시했습니다.

키워드 인자 순서의 중요성

이 PEP 논의의 일환으로, 키워드 인자의 순서 정보가 중요한지, 그리고 인덱스와 키를 임의의 순서로 배치할 수 있는지(예: a[1,Z=3,2,R=4])를 결정하는 것이 중요합니다. PEP 468은 ordereddict 사용을 제안하여 첫 번째 논점을 다루려고 하지만, 인덱싱의 키워드 인자는 함수 호출의 kwargs와 동등하므로 현재로서는 마찬가지로 순서가 없고 같은 제약을 가진다고 받아들이는 편이 자연스러울 것입니다.

동작의 일관성 필요성

“새 인자 내용” 전략과 관련하여, Ian Cordasco는 논평에서 다음과 같이 지적합니다.

“단 하나의 메서드만 Python의 표준 동작과 완전히 다르게 동작하는 것은 불합리합니다. __getitem__만(그리고 표면상 __setitem__도) 키워드 인자를 받되, 이를 하나의 딕셔너리로 만드는 대신 개별적인 단일 항목 딕셔너리들로 만든다면 혼란스러울 것입니다.” 저희는 그의 논점에 동의하지만, 전달되는 인자에 관해서는 __getitem__이 이미 어떤 면에서 특별하다는 점을 지적해야 합니다.

Chris Angelico도 다음과 같이 말합니다.

“처음에는 “자, 함수 호출처럼 인덱싱에도 키워드 인자를 전달할 수 있는 선택지를 줍시다”라고 말해 놓고, 다시 돌아와서 “아, 하지만 함수 호출과 달리 이것들은 본질적으로 순서가 있고 매우 다르게 전달됩니다”라고 말하는 것은 매우 이상해 보입니다.” 이 점에서도 저희는 동의합니다. 일관성을 유지하는 가장 간단한 전략은 __getitem__에 **kwargs 인자를 허용하는 “kwargs 인자” 전략일 것입니다.

저자 중 한 명(Stefano Borini)은 “엄격한 딕셔너리” 전략만이 구현할 가치가 있다고 생각합니다. 이 전략은 모호하지 않고, 단순하며, 복잡한 파싱을 강요하지 않고, 위치나 이름으로 축을 참조하는 문제를 해결합니다. “옵션” 사용 사례는 아마도 다른 접근 방식으로 처리하는 것이 가장 좋으며, 이 PEP와는 무관할 수 있습니다. 대안인 “네임드 튜플”도 또 하나의 유효한 선택지입니다.

기본값 대체가 있는 인덱싱으로 .get()이 쓸모없어지는 문제

“default” 키워드를 도입하면 dict.get()이 쓸모없어질 수 있으며, 이는 d["key", default=3]으로 대체될 것입니다. 그러나 Chris Angelico는 다음과 같이 말합니다.

“현재는 __getitem__(문제를 발견하면 예외를 발생시킵니다)에 더해 다른 무언가, 예를 들어 대신 기본값을 반환하는 get()을 작성해야 합니다. 당신의 제안대로라면 두 브랜치가 모두 __getitem__ 안에 들어가게 되므로 코드를 공유할 수 있습니다. 하지만 여전히 두 개의 브랜치가 필요합니다.”

또한 Chris는 이어서 다음과 같이 말합니다.

“사람들을 하나의 일관된 이름으로 이끄는 강한 힘이 없다면, 즉흥적이고 상당히 임의적인 이름들이 난립하게 될 것입니다(어떤 사람은 default=를 쓰고, 다른 사람은 그것이 너무 길다며 def=를 쓰려 하겠지만 그것은 키워드이므로 dflt= 같은 것을 쓰게 될 것입니다…).”.

이 인자(논거)는 타당하지만 모든 함수 호출에도 똑같이 적용되는 것이며, 일반적으로 확립된 관례와 문서화를 통해 해결됩니다.

표기법의 퇴화에 관하여

사용자 Drekin은 다음과 같은 의견을 남겼습니다. “a[Z=3]과 a[{"Z": 3}]의 경우는 현재의 a[1, 2]와 a[(1, 2)]의 경우와 비슷합니다. 괄호는 사실 튜플 표기법의 일부가 아니라 문법상 필요할 뿐이라고 주장할 수도 있겠지만, 함수 호출과 비교하면 표기법의 퇴화처럼 보일 수 있습니다. f(1, 2)는 f((1, 2))와 같지 않습니다.”.

참고 문헌