[Rejected] PEP 472 - Support for indexing with keyword arguments

원문 링크: PEP 472 - Support for indexing with keyword arguments

상태: Rejected 유형: Standards Track 작성일: 24-Jun-2014

PEP 472 – 키워드 인수를 사용한 인덱싱 지원 (Rejected)

  • 작성자: Stefano Borini, Joseph Martinot-Lagarde
  • 논의 채널: Python-Ideas list
  • 상태: Rejected (거부됨)
  • 유형: Standards Track
  • 생성일: 2014년 6월 24일
  • Python 버전: 3.6
  • 최종 결정: Python-Dev 메시지

개요 (Abstract)

이 PEP는 인덱싱(indexing) 작업이 키워드 인수(keyword arguments)를 지원하도록 확장하는 것을 제안합니다. a[K=3,R=2]와 같은 표기법이 유효한 문법이 되도록 하는 것이 목표입니다. 향후 확장성을 고려하여 a[1:2, K=3, R=4]와 같은 복합적인 형태도 허용될 수 있으며, 이는 구현 방식에 따라 달라질 수 있습니다. 파서(parser) 변경 외에도 인덱스 프로토콜(__getitem__, __setitem__, __delitem__) 역시 잠재적으로 수정이 필요할 것입니다.

동기 (Motivation)

인덱싱 문법은 메서드 호출(method call)과 구별되는 강력한 의미론적 내용(semantic content)을 가집니다. 즉, 데이터의 부분집합(subset of data)을 참조한다는 것을 내포합니다. 제안자들은 이러한 의미론적 연관성이 중요하며, 이 데이터를 참조하는 허용된 전략을 확장하고자 합니다.

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

현재 Python 문법은 축에 대한 연관성을 표현하기 위해 전적으로 위치(position)에만 초점을 맞추고 있으며, 비점선형(non-punctiform) 선택(슬라이스, slices)을 참조하기 위한 문법적 설탕(syntactic sugar)도 포함합니다.

>>> a[3] # a의 네 번째 요소 반환
>>> a[1:10:2] # 슬라이스 표기법 (중요하지 않은 데이터 부분집합 추출)
>>> a[3,2] # 다차원 배열을 위한 여러 인덱스

이 PEP에서 제안하는 추가 표기법은 인덱싱 작업에서 키워드 인수를 포함하는 표기법을 허용합니다. 예를 들어:

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

이는 관습적인 이름(conventional names)을 사용하여 축(axes)을 참조할 수 있도록 합니다.

또한 위치 인수와 키워드 인수를 모두 허용하는 확장된 형태도 고려해야 합니다.

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

이 PEP는 이러한 표기법의 사용을 가능하게 하는 다양한 전략을 탐색할 것입니다.

사용 사례 (Use cases)

다음 실용적인 사용 사례들은 키워드를 사용한 명세의 두 가지 넓은 범주를 제시합니다: 인덱싱(Indexing) 및 문맥적 옵션(contextual option).

인덱싱 용도:

  • 인덱스에 더 명확한 의미를 부여하여, 예를 들어 인덱스의 의도치 않은 반전을 방지합니다.
    >>> gridValues[x=3, y=5, z=8]
    >>> rain[time=0:12, location=location]
    
  • 계산 물리학 및 화학과 같은 일부 도메인에서는 Basis[Z=5]와 같은 표기법이 특정 수준의 정확도를 나타내는 도메인 특정 언어(Domain Specific Language) 표기법으로 사용됩니다.
    >>> low_accuracy_energy = computeEnergy(molecule, BasisSet[Z=3])
    

    이 경우, 인덱스 작업은 선택된 정확도 수준(매개변수 Z로 표현됨)의 기준 집합(basis set)을 반환할 것입니다. 인덱싱 뒤에 있는 이유는 BasisSet 객체가 내부적으로 숫자 테이블로 표현될 수 있기 때문입니다. 여기서 행(이 예에서는 사용자에게 숨겨진 “계수” 축)은 개별 요소에 연결되고(예: 행 0:5는 요소 1의 계수를 포함하고, 행 5:8은 요소 2의 계수를 포함합니다), 각 열은 주어진 정확도(“정확도” 또는 “Z” 축)와 연결되어 첫 번째 열은 낮은 정확도, 두 번째 열은 중간 정확도 등으로 구성됩니다. 이러한 인덱싱을 통해 사용자는 정확도 수준 3에 해당하는 내부 테이블의 열 내용을 나타내는 다른 객체를 얻게 됩니다.

문맥적 옵션 용도:

  • “default” 옵션을 사용하여 인덱스가 존재하지 않을 때 기본 반환 값(default return value)을 지정할 수 있습니다.
    >>> lst = [1, 2, 3]
    >>> value = lst[5, default=0] # value는 0
    
  • 희소 데이터셋(sparse dataset)의 경우, 예를 들어 주변 데이터에서 누락된 점을 추론하기 위한 보간 전략(interpolation strategy)을 지정할 수 있습니다.
    >>> value = array[1, 3, interpolate=spline_interpolator]
    
  • 동일한 메커니즘으로 단위를 지정할 수 있습니다.
    >>> value = array[1, 3, unit="degrees"]
    
  • 이러한 표기법이 어떻게 해석될지는 이를 구현하는 클래스에 달려 있습니다.

현재 구현 (Current implementation)

현재 인덱싱 작업은 __getitem__, __setitem__, __delitem__ 메서드에 의해 처리됩니다. 이 메서드들의 시그니처(signature)는 인덱스에 대해 하나의 인수만 허용합니다 (__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]와 같은 슬라이싱 표기법은 slice 객체를 생성하거나, 여러 값이 전달된 경우 slice 객체를 포함하는 튜플을 생성합니다.

slice 표기법을 처리하는 고유한 능력을 제외하면, 인덱싱 작업은 일반 메서드 호출과 유사점이 있습니다. 단일 요소로 호출될 때는 메서드처럼 작동합니다. 요소 수가 하나보다 많으면 idx 인수는 *args처럼 작동합니다. 그러나 동기 섹션에서 언급했듯이, 인덱싱 작업은 더 큰 집합에서 부분집합을 추출한다는 강력한 의미론적 함의를 가지며, 이는 적절한 이름이 선택되지 않는 한 일반 메서드 호출에 자동으로 연결되지 않습니다. 또한, 그 다른 시각적 스타일은 가독성에 중요합니다.

명세 (Specifications)

구현은 __getitem__의 현재 시그니처를 보존하거나, 하위 호환성(backward-compatible) 방식으로 수정해야 합니다. 처리해야 할 가능한 사례들을 고려하여 다양한 대안을 제시할 것입니다.

  • C0. a[1]; a[1,2] # 전통적인 인덱싱
  • 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] # 중간에 삽입된 순서

전략 “엄격한 딕셔너리” (Strategy “Strict dictionary”)

이 전략은 __getitem__이 하나의 객체만 허용한다는 점과, 그 객체의 성격이 축의 명세에서 모호하지 않아야 한다는 점을 인정합니다. 즉, 순서(order) 또는 이름(name)으로 지정될 수 있습니다. 이러한 가정의 결과로, 키워드 인수가 있을 때 전달되는 엔티티는 딕셔너리(dictionary)이며 모든 레이블이 명시되어야 합니다.

  • 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] -> SyntaxError 발생
  • C4. a[1, Z=3, R=4] -> SyntaxError 발생
  • C5. a[1, 2, Z=3] -> SyntaxError 발생
  • C6. a[1, 2, Z=3, R=4] -> SyntaxError 발생
  • C7. a[1, Z=3, 2, R=4] -> SyntaxError 발생

장점 (Pros)

  • 튜플(tuple) 사례와 딕셔너리(dictionary) 사례 간의 강력한 개념적 유사성. 첫 번째 경우에는 튜플을 지정하므로 쉼표로 구분된 일반 값 집합을 자연스럽게 정의합니다. 두 번째 경우에는 딕셔너리를 지정하므로 dict(Z=3, R=4)와 같이 동질적인 키/값 쌍 집합을 지정합니다.
  • __getitem__ 측에서 간단하고 파싱하기 쉽습니다. 튜플을 받으면 위치를 사용하여 축을 결정합니다. 딕셔너리를 받으면 키워드를 사용합니다.
  • C 인터페이스 변경이 필요하지 않습니다.

중립 (Neutral)

  • a[{"Z": 3, "R": 4}]a[Z=3, R=4]의 중복성(degeneracy)은 이 표기법이 문법적 설탕임을 의미합니다.

단점 (Cons)

  • 매우 엄격합니다. 전달된 인수의 순서를 파괴합니다. PEP 468에서 제안된 OrderedDict를 사용하면 순서 유지가 가능했을 것입니다.
  • a[1, 2, default=5]와 같이 위치/키워드 인수가 혼합된 사용 사례를 허용하지 않습니다.

전략 “혼합 딕셔너리” (Strategy “mixed dictionary”)

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

  • 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}

장점 (Pros)

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

단점 (Cons)

  • 문자열 키(string keys)의 순서 정보를 파괴합니다. C7에서 “Z”가 위치 1에 있었는지 3에 있었는지 알 방법이 없습니다.
  • 하나의 지정된 인덱스에 키워드 인수가 포함되는 즉시 튜플에서 딕셔너리로 전환됨을 의미합니다.
  • 파싱(parsing)이 혼란스러울 수 있습니다.

전략 “이름 붙은 튜플” (Strategy “named tuple”)

idx에 튜플 대신 namedtuple을 반환합니다. 키워드 인수는 명시된 이름을 키로 가지며, 위치 인수는 순서 앞에 밑줄(_)이 붙습니다.

  • 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) 또는 (_0=1, Z=3, _2=2, R=4) 또는 SyntaxError 발생

namedtuple의 필수 타입 이름은 Index 또는 함수 정의의 인수 이름일 수 있으며, 순서를 유지하고 _fields 속성을 사용하여 분석하기 쉽습니다. 하나 이상의 항목을 가진 C0이 이제 일반 튜플 대신 namedtuple을 전달한다는 점을 제외하면 하위 호환성을 유지합니다.

장점 (Pros)

  • 보기 좋습니다.
  • namedtuple은 투명하게 tuple을 대체하고 이전 동작으로 우아하게 저하(gracefully degrades)됩니다.
  • C 인터페이스 변경이 필요하지 않습니다.

단점 (Cons)

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

전략 “새로운 인수 내용” (Strategy “New argument contents”)

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

이 전략을 구현할 수 있는 네 가지 가능한 방법을 식별했습니다.

  • P1: 키워드 인수에 대해 단일 딕셔너리를 사용합니다.
  • P2: 개별 단일 항목 딕셔너리(single-item dictionaries)를 사용합니다.
  • P3: P2와 유사하지만, 단일 항목 딕셔너리를 (키, 값) 튜플로 대체합니다.
  • P4: P2와 유사하지만, 특별하고 추가적인 새 객체 keyword()를 사용합니다.

이러한 가능성 중 일부는 중복된 표기법(degenerate notations), 즉 이미 가능한 표현과 구별할 수 없는 표기법으로 이어집니다. 다시 말하지만, 제안된 표기법은 이러한 표현에 대한 문법적 설탕이 됩니다.

이 전략 하에서는 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 단일 키를 가진 딕셔너리 또는 idx = (("Z", 3),) # P3 튜플의 튜플 또는 idx = keyword("Z", 3) # P4 keyword 객체

보시다시피, P1/P2 표기법은 a[Z=3]a[{"Z": 3}]가 정확히 동일한 값을 전달하며 __getitem__을 호출한다는 것을 의미하므로, 후자에 대한 문법적 설탕입니다. P3에서도 인덱스가 다르지만 동일한 상황이 발생합니다. P4와 같이 keyword 객체를 사용하면 이러한 중복성을 제거할 수 있습니다.

C2의 경우:

  • C2. a[Z=3, R=4] -> idx = {"Z": 3, "R": 4} # P1 딕셔너리/OrderedDict 또는 idx = ({"Z": 3}, {"R": 4}) # P2 두 개의 단일 키 딕셔너리 튜플 또는 idx = (("Z", 3), ("R", 4)) # P3 튜플의 튜플 또는 idx = (keyword("Z", 3), keyword("R", 4) ) # P4 keyword 객체

P1은 전통적인 **kwargs 동작에 자연스럽게 매핑되지만, 인덱스에 두 개 이상의 항목이 튜플을 생성한다는 관례를 disruptive니다. P2는 이 동작을 보존하며, 추가적으로 순서를 보존합니다. PEP 468에서 제안된 OrderedDict를 사용하면 순서 보존이 가능했을 것입니다.

나머지 사례들은 다음과 같습니다.

  • C3. a[1, Z=3] -> idx = (1, {"Z": 3}) # P1/P2 또는 idx = (1, ("Z", 3)) # P3 또는 idx = (1, keyword("Z", 3)) # P4
  • C4. a[1, Z=3, R=4] -> idx = (1, {"Z": 3, "R": 4}) # P1 또는 idx = (1, {"Z": 3}, {"R": 4}) # P2 또는 idx = (1, ("Z", 3), ("R", 4)) # P3 또는 idx = (1, keyword("Z", 3), keyword("R", 4)) # P4
  • C5. a[1, 2, Z=3] -> idx = (1, 2, {"Z": 3}) # P1/P2 또는 idx = (1, 2, ("Z", 3)) # P3 또는 idx = (1, 2, keyword("Z", 3)) # P4
  • C6. a[1, 2, Z=3, R=4] -> idx = (1, 2, {"Z":3, "R": 4}) # P1 또는 idx = (1, 2, {"Z": 3}, {"R": 4}) # P2 또는 idx = (1, 2, ("Z", 3), ("R", 4)) # P3 또는 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. 키워드 인수들을 묶습니다. 보기 좋지 않습니다. 또는 SyntaxError 발생 # P1. 함수 호출과 동일한 동작. 또는 idx = (1, {"Z": 3}, 2, {"R": 4}) # P2 또는 idx = (1, ("Z", 3), 2, ("R", 4)) # P3 또는 idx = (1, keyword("Z", 3), 2, keyword("R": 4)) # P4

장점 (Pros)

  • 시그니처가 변경되지 않습니다.
  • P2/P3는 인덱싱 시 지정된 키워드 인수의 순서를 보존할 수 있습니다. P1은 OrderedDict가 필요하지만, 허용될 경우 중간에 삽입된 순서(interposed ordering)를 파괴할 것입니다. 모든 키워드 인덱스는 딕셔너리에 덤핑될 것입니다.
  • 전통적인 타입인 튜플과 딕셔너리(및 OrderedDict) 내에 머무릅니다.
  • 일부 제안된 전략은 전통적인 함수 호출과 유사한 동작을 보입니다.
  • PyObject_GetItem 및 관련 루틴에 대한 C 인터페이스는 변경되지 않습니다.

단점 (Cons)

  • 겉보기에 복잡하고 비효율적입니다.
  • 표기법의 중복성(예: a[Z=3]a[{"Z":3}]__[get|set|del]item__ 수준에서 동등하며 구별할 수 없습니다). 이 동작은 허용될 수도 있고 그렇지 않을 수도 있습니다.
  • P4의 경우, 위 중복성을 모호하게 하지 않기 위해 slice()와 유사한 추가 객체가 필요합니다.
  • idx의 타입과 레이아웃이 호출자의 “변덕”에 따라 변경되는 것 같습니다.
  • 특히 튜플의 튜플인 경우, 전달된 내용을 파싱하기 복잡할 수 있습니다.
  • P2는 튜플의 멤버로 많은 단일 키 딕셔너리를 생성합니다. 보기 좋지 않습니다. P3는 딕셔너리 튜플보다 가볍고 사용하기 쉽지만, 키워드 추출이 번거로울 것입니다.

전략 “kwargs 인수” (Strategy “kwargs argument”)

__getitem__은 키워드 전용(keyword-only)이어야 하는 선택적 **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] -> SyntaxError 발생 # 함수 동작과 일치

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

장점 (Pros)

  • 함수 호출과 유사하며, 자연스럽게 발전합니다.
  • __getitem__kwargs가 없는 객체에 키워드 인덱싱을 사용하면 명확하게 실패합니다. 이는 다른 전략에서는 그렇지 않습니다.

단점 (Cons)

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

C 인터페이스 (C interface)

이전 분석에서 간략히 소개되었듯이, 새로운 기능을 허용하기 위해 C 인터페이스가 잠재적으로 변경되어야 할 것입니다. 특히, “kwargs 인수” 전략의 경우 PyObject_GetItem 및 관련 루틴은 추가 PyObject *kw 인수를 받아야 할 것입니다. 나머지 전략들은 C 함수 시그니처 변경을 요구하지 않지만, 전달되는 객체의 다른 특성으로 인해 잠재적으로 조정이 필요할 것입니다.

“named tuple” 전략은 아무런 변경 없이 올바르게 작동할 것입니다. collections 모듈의 팩토리 메서드가 반환하는 클래스는 tuple의 서브클래스이므로, PyTuple_* 함수가 결과 객체를 처리할 수 있습니다.

대체 솔루션 (Alternative Solutions)

이 섹션에서는 제안된 기능이 없어도 문제없이 작동할 수 있는 대체 솔루션을 제시하며, 제안된 개선 사항이 구현할 가치가 없음을 시사합니다.

메서드 사용 (Use a method)

기본 인덱싱으로 충분하지 않은 경우, 현재 인덱싱 방식을 유지하고 전통적인 get() 메서드를 사용할 수 있습니다. 이는 좋은 점이지만, 서문에서 이미 언급했듯이 메서드는 인덱싱과 다른 의미론적 비중을 가지며, 메서드에서는 슬라이스를 직접 사용할 수 없습니다. 예를 들어 a[1:3, Z=2]a.get(slice(1,3), Z=2)를 비교해 보세요.

그러나 저자들은 이 주장이 설득력이 있으며, 키워드 기반 인덱싱의 의미론적 표현력 이점이 드물게 사용되고 충분한 이점을 제공하지 않으며 채택률이 낮을 수 있는 기능으로 인해 상쇄될 수 있음을 인정합니다.

슬라이스 객체 남용을 통한 요청 동작 에뮬레이션 (Emulate requested behavior by abusing the slice object)

이 매우 창의적인 방법은 문자열을 사용하거나 (또는 키에 대해 적절히 이름이 지정된 플레이스홀더 객체를 인스턴스화하는 것을 수락하고) “=” 대신 “:”를 사용하는 것을 수락한다면 slice 객체의 동작을 활용합니다.

>>> 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"]로도 얻을 수 있는데, 이는 이상하며, 인덱싱에서 키워드의 의도된 의미, 즉 위치보다는 관습적인 이름으로 축을 지정하는 것과 충돌합니다.

딕셔너리를 추가 인덱스로 전달 (Pass a dictionary as an additional index)

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

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

추가 의견 (Additional Comments)

논의 참여자들은 다음의 관련 의견들도 표명했습니다.

키워드 인수의 순서 관련성 (Relevance of ordering of keyword arguments)

이 PEP 논의의 일환으로, 키워드 인수의 순서 정보가 중요한지, 그리고 인덱스와 키가 임의의 방식으로 정렬될 수 있는지(a[1,Z=3,2,R=4]와 같이) 결정하는 것이 중요합니다. PEP 468은 OrderedDict 사용을 제안하여 첫 번째 지점을 다루려고 하지만, 인덱싱의 키워드 인수는 함수 호출의 kwargs와 동등하며, 따라서 오늘날과 같이 순서가 없고 동일한 제약 조건을 가진다고 받아들이는 경향이 있을 것입니다.

동작의 동질성 필요성 (Need for homogeneity of behavior)

“새로운 인수 내용” 전략과 관련하여, Ian Cordasco의 의견은 다음과 같습니다.

“Python의 표준 동작과 완전히 다르게 작동하는 메서드가 하나뿐이라는 것은 불합리할 것입니다. __getitem__ (그리고 명목상 __setitem__)만이 키워드 인수를 받지만, 그것들을 딕셔너리로 바꾸는 대신 개별 단일 항목 딕셔너리로 바꾼다는 것은 혼란스러울 것입니다.”

우리는 그의 의견에 동의하지만, __getitem__은 전달되는 인수에 관해서는 이미 여러 면에서 특별하다는 점을 지적해야 합니다.

Chris Angelico도 다음과 같이 말했습니다.

“함수 호출과 마찬가지로 인덱싱에 키워드 인수를 전달할 옵션을 제공하자고 시작한 다음, ‘아, 하지만 함수 호출과 달리, 그것들은 본질적으로 순서가 있고 매우 다르게 전달됩니다’라고 말하는 것은 매우 이상하게 보입니다.”

다시 한번, 우리는 이 의견에 동의합니다. 동질성을 유지하는 가장 간단한 전략은 __getitem__**kwargs 인수를 여는 “kwargs 인수” 전략일 것입니다.

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

기본 폴백(default fallback)을 사용한 인덱싱을 위해 .get()이 쓸모없게 됨 (Having .get() become obsolete for indexing with default fallback)

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

“현재는 문제를 발견하면 예외를 발생시키는 __getitem__과, 대신 기본값을 반환하는 get()과 같은 다른 것을 작성해야 합니다. 당신의 제안에 따르면, 두 분기 모두 __getitem__ 내부에 들어가므로 코드를 공유할 수 있지만, 여전히 두 분기가 필요합니다.”

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

“일관된 이름으로 사람들을 밀어붙이는 강력한 힘이 없다면, default=로 갈지, 너무 길다고 생각하여 def=로 갈지 (이는 키워드이므로 dflt= 등을 사용할 것임)에 대한 임시적이고 상당히 자의적인 이름의 웅덩이가 생길 것입니다.”

이 주장은 유효하지만, 모든 함수 호출에도 똑같이 유효하며, 일반적으로 확립된 관례와 문서화를 통해 해결됩니다.

표기법의 중복성에 대하여 (On degeneracy of notation)

사용자 Drekin은 다음과 같이 언급했습니다.

a[Z=3]a[{"Z": 3}]의 경우는 현재의 a[1, 2]a[(1, 2)]와 유사합니다. 괄호가 실제로 튜플 표기법의 일부가 아니라 단지 구문 때문에 필요하다고 주장할 수 있지만, 함수 호출과 비교할 때 표기법의 중복성으로 보일 수 있습니다: f(1, 2)f((1, 2))와 같지 않습니다.”

참고 문헌 (References)

  • “keyword-only args in __getitem__
  • “Accepting keyword arguments for __getitem__
  • “PEP pre-draft: Support for indexing with keyword arguments”

이 문서는 공용 도메인에 공개되었습니다.

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments