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 객체가 내부적으로 숫자 테이블로 표현될 수 있기 때문입니다. 이 테이블에서 행(이 예에서는 사용자에게 숨겨진 “coefficient” 축)은 개별 원소와 연결됩니다(예: 0:5행에는 원소 1의 계수가 포함되고, 5:8행에는 원소 2의 계수가 포함됨). 또한 각 열은 특정 정확도 수준(“accuracy” 또는 “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]의 경우 idx2가 됩니다.
  • 여러 요소가 전달되면 쉼표로 구분해야 합니다: 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 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

필요한 네임드 튜플 타입명은 Index 또는 함수 정의에 있는 인자의 이름이 될 수 있으며, 순서를 유지하고 _fields속성을 사용하여 쉽게 분석할 수 있습니다. C0에 항목이 둘 이상 있는 경우 일반 튜플 대신 네임드 튜플을 전달한다는 조건하에 하위 호환성이 유지됩니다.

장점

  • 보기 좋습니다. 네임드 튜플은 튜플을 투명하게 대체하며 이전 동작으로 우아하게 폴백합니다.
  • C 인터페이스를 변경할 필요가 없습니다.

단점

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

전략 “새 인자 내용”

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

이 전략을 구현하는 네 가지 가능한 방법을 식별합니다:

  • P1은 키워드 인자에 단일 딕셔너리를 사용합니다.
  • P2는 각각 하나의 항목만 포함하는 개별 딕셔너리를 사용합니다.
  • P3P2와 유사하지만, 하나의 항목만 포함하는 딕셔너리를 (key, value) 튜플로 대체합니다.
  • P4P2와 유사하지만, 특수하고 추가적인 새 객체인 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 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 인터페이스는 변경되지 않습니다.

단점

  • 겉보기에는 복잡하고 낭비가 많습니다.
  • 표기법의 퇴화가 발생합니다(예를 들어, a[Z=3]a[{"Z":3}]는 동등하며 __[get|set|del]item__수준에서 구별할 수 없는 표기법입니다). 이러한 동작을 허용할 수 있는지 여부는 분명하지 않습니다.
  • P4의 경우 위의 퇴화를 구분하기 위해서만 slice()와 본질적으로 유사한 추가 객체가 필요합니다.
  • idx의 유형과 배치는 호출자의 변덕에 따라 달라지는 것처럼 보입니다;
  • 전달되는 내용을 구문 분석하기가 복잡할 수 있으며, 특히 튜플의 튜플인 경우에는 그러합니다;
  • P2는 튜플의 멤버로 단일 키 딕셔너리를 많이 생성합니다. 보기 좋지 않습니다. P3는 딕셔너리 튜플보다 더 가볍고 사용하기 쉬우며, 일반 딕셔너리와 달리 순서도 보존하지만 키워드를 추출하기가 번거로워집니다.

전략 “kwargs argument”

__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 인터페이스를 변경해야 할 가능성이 있습니다. 구체적으로, PyObject_GetItem및 관련 루틴은 전략 “kwargs argument”를 위해 추가 PyObject *kw 인자를 받아들여야 합니다. 나머지 전략에는 C 함수 시그니처의 변경이 필요하지 않지만, 전달되는 객체의 성격이 다르므로 조정이 필요할 가능성이 있습니다.

전략 “named tuple”은 아무런 변경 없이 올바르게 작동합니다. collections의 팩토리 메서드가 반환하는 클래스는 튜플의 서브클래스이므로 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}]

이 표기는 덜 우아하지만 이미 사용할 수 있으며 비슷한 결과를 얻습니다. 제안된 Strategy “New argument contents”는 이 표기의 구문 설탕으로 해석할 수 있음이 분명합니다.

추가 의견

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

키워드 인자 순서의 중요성

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

동작의 일관성 필요성

Strategy “New argument contents”와 관련하여 Ian Cordasco는 다음과 같이 지적합니다.

“Python의 표준 동작과 완전히 다르게 동작하는 메서드가 단 하나뿐인 것은 불합리합니다. __getitem__(그리고 아마도 __setitem__)만 키워드 인자를 받되, 이를 딕셔너리로 변환하는 대신 개별 단일 항목 딕셔너리로 변환한다면 혼란스러울 것입니다.” 저희는 그의 의견에 동의하지만, 전달된 인자와 관련하여 __getitem__은 이미 일부 측면에서 특별하다는 점을 지적해야 합니다.

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

“처음에는 ‘여기, 함수 호출과 마찬가지로 인덱싱에도 키워드 인자를 포함할 수 있는 선택지를 제공합시다’라고 해 놓고, 나중에는 ‘아, 하지만 함수 호출과 달리 키워드 인자는 본질적으로 순서가 있고 전혀 다르게 전달됩니다’라고 말하는 것은 매우 이상해 보입니다.” 다시 말해, 이 점에 대해서도 저희는 동의합니다. 일관성을 유지하는 가장 간단한 전략은 Strategy “kwargs argument”이며, __getitem__**kwargs 인자를 여는 것입니다.

저자 중 한 명인 Stefano Borini는 “엄격한 딕셔너리” 전략만 구현할 가치가 있다고 생각합니다. 이는 모호하지 않고 간단하며 복잡한 구문 분석을 강제하지 않고, 위치 또는 이름으로 축을 참조하는 문제를 해결합니다. “options” 사용 사례는 다른 접근 방식으로 처리하는 것이 가장 적절할 가능성이 높으며, 이 PEP와 관련이 없을 수도 있습니다. 대안인 “named tuple”도 유효한 선택지입니다.

기본값 대체를 사용하는 인덱싱에서 .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))와 같지 않습니다.”

참고 자료