[Rejected] PEP 225 - Elementwise/Objectwise Operators

원문 링크: PEP 225 - Elementwise/Objectwise Operators

상태: Rejected 유형: Standards Track 작성일: 19-Sep-2000

경고: 이 PEP는 거부되었습니다. 이 PEP 대신 PEP 465에서 제시된 접근 방식이 최종적으로 채택되었습니다. PEP 465의 ‘거부된 아이디어(Rejected Ideas)’ 섹션에서 이 결정의 배경에 대한 더 자세한 설명을 확인할 수 있습니다.

1. 개요 (Introduction)

이 PEP는 파이썬(Python)에 요소별 (elementwise)객체별 (objectwise) 연산을 구분하는 데 유용한 새로운 연산자를 추가하자는 제안을 설명합니다. 또한, comp.lang.python 뉴스 그룹에서 이 주제에 대해 논의된 내용을 요약합니다.

주요 논의 내용은 다음과 같습니다.

  • 배경 (Background)
  • 제안된 연산자 및 구현 문제 설명
  • 새로운 연산자에 대한 대안 분석
  • 대안적 형태 분석
  • 호환성 문제
  • 더 넓은 확장 및 기타 관련 아이디어 설명

이 PEP의 상당 부분은 제안된 확장에는 포함되지 않는 아이디어를 설명하고 있습니다. 이는 이 확장이 본질적으로 문법적 설탕 (syntactic sugar) 이므로, 다양한 대안들과 비교하여 채택 여부를 저울질해야 했기 때문입니다.

요소별-객체별 연산에 대한 문제는 수치 계산 (numerical computation)을 넘어 더 넓은 영역으로 확장될 수 있습니다. 이 문서에서는 현재 제안이 더 일반적인 미래 확장과 어떻게 통합될 수 있는지도 설명합니다.

2. 배경 (Background)

파이썬은 여섯 가지 이항 중위 수학 연산자 + - * / % **를 제공하며, 이를 op로 통칭합니다. 이러한 연산자들은 사용자 정의 클래스 (user-defined classes)에서 새로운 의미로 오버로드 (overload) 될 수 있습니다.

그러나 배열 (arrays), 벡터 (vectors), 행렬 (matrices)과 같은 동질적인 요소들로 구성된 객체의 경우, 본질적으로 두 가지 다른 의미론이 존재합니다.

  • 객체별 연산 (Objectwise operations): 이러한 객체들을 다차원 공간의 점으로 취급합니다. (예: 행렬 곱셈)
  • 요소별 연산 (Elementwise operations): 이러한 객체들을 개별 요소들의 컬렉션으로 취급합니다. (예: 요소별 곱셈)

이 두 가지 연산은 같은 수식에 자주 혼합되어 사용되기 때문에, 문법적인 구분이 필요합니다.

많은 수치 계산 언어는 두 가지 수학 연산자 집합을 제공합니다. 예를 들어, MatLab에서는 일반적인 op가 객체별 연산에 사용되는 반면, .op는 요소별 연산에 사용됩니다. R에서는 op가 요소별 연산을 의미하는 반면, %op%는 객체별 연산을 의미합니다.

파이썬에서는 이러한 연산을 표현하는 다른 방법들이 있으며, 일부는 이미 기존 수치 패키지에서 사용되고 있습니다.

  • 함수 (function): mul(a, b)
  • 메서드 (method): a.mul(b)
  • 캐스팅 (casting): a.E * b

하지만 이러한 방법들은 중위 연산자 (infix operators)만큼 적절하지 않습니다. 주요 단점은 다음과 같습니다.

  • 가독성 (Readability): 적당히 복잡한 수식에서도 중위 연산자가 대안보다 훨씬 깔끔합니다.
  • 익숙함 (Familiarity): 사용자들은 일반적인 수학 연산자에 익숙합니다.
  • 구현 (Implementation): 새로운 중위 연산자는 파이썬 문법을 과도하게 복잡하게 만들지 않을 것이며, 수치 패키지의 구현을 크게 용이하게 할 것입니다.

현재 수학 연산자들을 하나의 의미론에 할당하는 것은 가능하지만, 다른 의미론을 위한 중위 연산자가 충분하지 않습니다. 또한, 한쪽이 일반적인 수학 연산자 기호를 포함하지 않는다면, 두 의미론 간의 시각적 대칭 (visual symmetry)을 유지하는 것도 불가능합니다.

3. 제안된 확장 (Proposed extension)

핵심 파이썬(core Python)에 여섯 가지 새로운 이항 중위 연산자 ~+ ~- ~* ~/ ~% ~**가 추가됩니다. 이들은 기존 연산자 + - * / % **와 유사합니다.

파이썬 2.0에서 사용 가능한 증강 할당 연산자 += -= *= /= %= **=와 유사하게 ~+= ~-= ~*= ~/= ~%= ~**= 여섯 가지 증강 할당 연산자도 핵심 파이썬에 추가됩니다.

  • 연산자 ~op는 연산자 op의 문법적 속성 (구분 선호도 포함)을 유지합니다.
  • 연산자 ~op는 내장 숫자 타입 (built-in number types)에 대해 연산자 op의 의미론적 속성을 유지합니다.
  • 연산자 ~op는 비숫자 내장 타입 (non-number built-in types)에 대해 SyntaxError를 발생시킵니다. 이는 적절한 동작이 합의될 때까지의 임시 조치입니다.

이러한 연산자들은 일반 수학 연산자의 이름 앞에 t (틸드(tilde)의 약어)를 붙인 이름으로 클래스에서 오버로드 (overload)할 수 있습니다. 예를 들어, __add____radd__+에 대해 작동하는 것처럼 __tadd____rtadd__~+에 대해 작동합니다. 기존 연산자와 마찬가지로, 왼쪽 피연산자가 적절한 메서드를 제공하지 않을 경우 __r*__() 메서드가 호출됩니다.

op 또는 ~op 중 한 세트가 요소별 연산에 사용되고, 다른 한 세트가 객체별 연산에 사용되도록 의도되었지만, 어떤 버전의 연산자가 요소별 또는 객체별 연산을 나타내는지는 명시적으로 지정되지 않으며, 결정은 애플리케이션에 맡겨집니다.

제안된 구현은 토크나이저 (tokenizer), 파서 (parser), 문법 (grammar), 컴파일러 (compiler) 관련 여러 파일을 패치하여 해당 기존 연산자의 기능을 필요에 따라 복제하는 것입니다. 모든 새로운 의미론은 이를 오버로드하는 클래스에서 구현됩니다.

~ 기호는 이미 파이썬에서 단항 비트wise not (bitwise not) 연산자로 사용됩니다. 현재 이항 연산자에는 허용되지 않습니다. 새로운 연산자들은 완전히 하위 호환됩니다.

4. 프로토타입 구현 (Prototype Implementation)

Greg Lielens는 파이썬 2.0b1 소스에 대한 패치로 중위 ~op를 구현했습니다.

~가 이항 연산자의 일부가 되도록 허용하기 위해, 토크나이저 (tokenizer)는 ~+를 하나의 토큰으로 처리합니다. 이는 현재 유효한 표현식인 ~+1~ + 1 대신 ~+ 1로 토큰화된다는 것을 의미합니다. 파서 (parser)는 ~+~ +의 합성으로 처리합니다. 이 효과는 애플리케이션에 보이지 않습니다.

현재 패치에 대한 참고사항:

  • 아직 ~op= 연산자는 포함하지 않습니다.
  • ~op는 예외를 발생시키는 대신 리스트 (lists)에서 op와 동일하게 동작합니다.

이러한 문제들은 이 제안의 최종 버전이 준비될 때 수정되어야 합니다.

이 패치는 xor를 다음과 같은 의미론을 갖는 중위 연산자로 예약합니다.

def __xor__(a, b):
    if not b: return a
    elif not a: return b
    else: 0

이는 가능한 한 참 값 (true value)을 보존하고, 그렇지 않다면 가능한 한 왼쪽 값 (left hand side value)을 보존합니다. 이는 향후 비트wise 연산자 (bitwise operators)가 요소별 논리 연산자 (elementwise logical operators)로 간주될 수 있도록 하기 위함입니다.

5. 새로운 연산자 추가에 대한 대안 (Alternatives to adding new operators)

comp.lang.pythonpython-dev 메일링 리스트에서 많은 대안이 논의되었습니다. 곱셈 연산자를 예시로 들어 몇 가지 주요 대안은 다음과 같습니다.

5.1. 함수 mul(a, b) 사용 (Use function mul(a, b))

  • 장점: 새로운 연산자가 필요 없습니다.
  • 단점:
    • 복합 수식에 대해 접두사 (prefix) 형태가 번거롭습니다.
    • 대상 사용자에게 익숙하지 않습니다.
    • 대상 사용자에게 너무 장황합니다.
    • 자연스러운 연산자 우선순위 규칙을 사용할 수 없습니다.

5.2. 메서드 호출 a.mul(b) 사용 (Use method call a.mul(b))

  • 장점: 새로운 연산자가 필요 없습니다.
  • 단점:
    • 양쪽 피연산자에 대해 비대칭적입니다.
    • 대상 사용자에게 익숙하지 않습니다.
    • 대상 사용자에게 너무 장황합니다.
    • 자연스러운 연산자 우선순위 규칙을 사용할 수 없습니다.

5.3. 쉐도우 클래스 (Shadow classes) 사용

행렬 클래스에 대해 .E 메서드를 통해 접근 가능한 쉐도우 배열 클래스를 정의하여, 행렬 ab에 대해 a.E * belementwise_mul(a, b)인 행렬 객체가 되도록 합니다. 마찬가지로, 배열에 대한 쉐도우 행렬 클래스를 .M 메서드를 통해 접근 가능하도록 정의하여, 배열 ab에 대해 a.M * bmatrixwise_mul(a, b)인 배열이 되도록 합니다.

  • 장점:
    • 새로운 연산자가 필요 없습니다.
    • 올바른 연산자 우선순위 규칙을 가진 중위 연산자의 이점을 얻습니다.
    • 애플리케이션에서 깔끔한 수식이 가능합니다.
  • 단점:
    • 현재 파이썬에서는 일반 숫자가 사용자 정의 클래스 메서드를 가질 수 없기 때문에 유지 관리가 어렵습니다. 즉, a가 순수 숫자이면 a.E * b가 실패합니다.
    • T (전치)와 같은 기존 메서드 호출과 충돌하기 때문에 구현이 어렵습니다.
    • 객체 생성 및 메서드 검색에 따른 런타임 오버헤드 (runtime overhead)가 발생합니다.
    • 쉐도잉 클래스는 진정한 클래스를 대체할 수 없습니다. 자체 타입을 반환하지 않기 때문입니다. 따라서 쉐도우 E 클래스를 가진 M 클래스와 쉐도우 M 클래스를 가진 E 클래스가 필요합니다.
    • 수학자들에게는 부자연스럽습니다.

5.4. 쉬운 캐스팅을 통한 행렬별/요소별 클래스 구현 (Implement matrixwise and elementwise classes with easy casting)

배열에 대한 행렬별 연산은 a.M * b.M과 같고, 행렬에 대한 요소별 연산은 a.E * b.E와 같도록 합니다. 오류 감지를 위해 a.E * b.M은 예외를 발생시킵니다.

  • 장점:
    • 새로운 연산자가 필요 없습니다.
    • 올바른 연산자 우선순위 규칙을 가진 중위 표기법과 유사합니다.
  • 단점:
    • 순수 숫자에 대한 사용자 메서드 부족으로 인한 유사한 어려움이 있습니다.
    • 객체 생성 및 메서드 검색에 따른 런타임 오버헤드가 발생합니다.
    • 더 복잡한 수식이 됩니다.
    • 연산자를 용이하게 하기 위해 객체의 속성을 전환하는 것이 지속됩니다. 이는 애플리케이션 코드에 유지 관리가 극도로 어려운 장거리 컨텍스트 종속성 (context dependencies)을 도입합니다.

5.5. 따옴표로 묶인 문자열에 임의의 확장으로 작성된 수식을 파싱하기 위한 미니 파서 (mini parser) 사용 (Using mini parser to parse formulas written in arbitrary extension placed in quoted strings)

  • 장점: 새로운 연산자 없이 순수 파이썬입니다.
  • 단점:
    • 실제 문법은 따옴표로 묶인 문자열 내부에 있어 문제 자체를 해결하지 못합니다.
    • 특수 문법 영역을 도입합니다.
    • 미니 파서에 대한 요구가 많습니다.

5.6. 행렬 곱셈을 위한 @와 같은 단일 연산자 도입 (Introducing a single operator, such as @, for matrix multiplication)

  • 장점: 더 적은 수의 연산자를 도입합니다.
  • 단점:
    • + - **와 같은 연산자들의 구분도 중요합니다.
    • 행렬 또는 배열 지향 패키지에서의 의미가 역전될 수 있습니다. (아래 참조)
    • 새로운 연산자가 특수 문자를 차지합니다.
    • 더 일반적인 객체-요소 문제에는 잘 작동하지 않습니다.

이러한 대안들 중에서 첫 번째와 두 번째는 현재 애플리케이션에서 어느 정도 사용되지만, 부적절하다고 판단되었습니다. 세 번째는 애플리케이션에서 가장 선호되지만, 엄청난 구현 복잡성을 초래할 것입니다. 네 번째는 애플리케이션 코드를 매우 컨텍스트에 민감하게 만들고 유지 관리가 어렵게 만들 것입니다. 이 두 대안은 또한 현재의 타입/클래스 분할로 인해 상당한 구현 어려움을 겪습니다. 다섯 번째는 해결하는 문제보다 더 많은 문제를 야기하는 것으로 보입니다. 여섯 번째는 동일한 범위의 애플리케이션을 포괄하지 않습니다.

6. 중위 연산자의 대안적 형태 (Alternative forms of infix operators)

새로운 중위 연산자의 두 가지 주요 형태와 여러 사소한 변형이 논의되었습니다.

6.1. 괄호형 (Bracketed form)

(op), [op], {op}, <op>, :op:, ~op~, %op%

6.2. 메타 문자형 (Meta character form)

.op, @op, ~op 또는 메타 문자가 연산자 뒤에 오는 형태도 논의되었습니다.

이러한 테마의 덜 일관된 변형들도 있었으나, 선호되지 않았습니다. 예를 들어,

  • 좌우 나눗셈을 위해 @// 사용
  • 외적 (outer product) 및 내적 (inner product)을 위해 [*](*) 사용
  • 곱셈을 위해 단일 연산자 @ 사용

표현 형태를 선택하는 기준은 다음과 같습니다.

  • 기존 연산자와의 문법적 모호성이 없어야 합니다.
  • 실제 수식에서 더 높은 가독성을 가져야 합니다. (이로 인해 괄호형이 불리해집니다.)
  • 기존 수학 연산자와 시각적으로 유사해야 합니다.
  • 미래 확장을 막지 않으면서 문법적으로 단순해야 합니다.

이러한 기준을 고려할 때, 괄호형 중에서는 {op}가 가장 유력한 것으로 나타났습니다. 메타 문자형 중에서는 ~op가 명확한 우승자였습니다. 이들을 비교해 보면 ~op가 모든 형태 중에서 가장 선호되었습니다.

몇 가지 분석은 다음과 같습니다.

  • .op 형태는 1.+a1 .+a와 다르게 해석될 수 있어 모호합니다.
  • 괄호형 연산자는 단독으로 있을 때는 가장 좋지만, 수식에서는 괄호의 시각적 파싱과 함수 인자 (function argument)와 간섭하여 가독성을 해칩니다. (op)[op]가 특히 그렇고, {op}<op>는 다소 덜합니다.
  • <op> 형태는 < >=와 혼동될 가능성이 있습니다.
  • @op@가 시각적으로 무겁기 때문에 (a@+ba@ + b보다 a @+ b로 더 쉽게 읽힘) 선호되지 않았습니다.
  • 메타 문자 선택에 있어서: 기존 ASCII 기호 대부분은 이미 사용되었습니다. 사용되지 않은 세 가지는 @ $ ?입니다.

7. 새로운 연산자의 의미론 (Semantics of new operators)

두 가지 연산자 세트 (기존 op와 제안된 ~op) 중 어느 것을 객체별 또는 요소별 연산에 사용할지에 대한 설득력 있는 주장이 모두 있었습니다.

7.1. op는 요소별, ~op는 객체별

  • Numeric 패키지의 현재 multiarray 인터페이스와 일관적입니다.
  • 일부 다른 언어와 일관적입니다.
  • 요소별 연산이 더 자연스럽다는 인식.
  • 요소별 연산이 더 자주 사용된다는 인식.

7.2. op는 객체별, ~op는 요소별

  • MatPy 패키지의 현재 선형 대수 인터페이스와 일관적입니다.
  • 일부 다른 언어와 일관적입니다.
  • 객체별 연산이 더 자연스럽다는 인식.
  • 객체별 연산이 더 자주 사용된다는 인식.
  • 리스트 (lists)에 대한 연산자의 현재 동작과 일관적입니다.
  • ~가 미래 확장에서 일반적인 요소별 메타 문자 (elementwise meta-character)가 될 수 있도록 허용합니다.

일반적으로 다음과 같은 점들이 동의되었습니다.

  • 어느 한쪽을 선호할 절대적인 이유는 없습니다.
  • 상당한 크기의 코드 덩어리에서 한 표현 방식에서 다른 표현 방식으로 쉽게 캐스팅할 수 있으므로, 다른 연산자 형태는 항상 소수일 것입니다.
  • 연산자가 통일되더라도 배열 지향 (array-oriented) 및 행렬 지향 (matrix-oriented) 패키지의 존재를 선호하는 다른 의미론적 차이점이 있습니다.
  • 어떤 결정이 내려지든, 기존 인터페이스를 사용하는 코드가 오랫동안 깨져서는 안 됩니다.

따라서, 이 두 연산자 세트의 의미론적 특성이 핵심 언어에 의해 강제되지 않는다면, 잃는 것은 많지 않고 많은 유연성이 유지됩니다. 애플리케이션 패키지가 가장 적합한 선택을 할 책임이 있습니다. NumPy와 MatPy는 이미 반대 의미론을 사용하고 있으며, 이는 현재도 마찬가지입니다. 새로운 연산자를 추가해도 이를 깨뜨리지 않을 것입니다.

수치 정밀도 (numerical precision) 문제가 제기되었지만, 의미론이 애플리케이션에 맡겨진다면 실제 정밀도도 애플리케이션에 속해야 합니다.

8. 예시 (Examples)

다음은 위에 설명된 다양한 연산자 또는 다른 표현 방식을 사용하여 나타날 실제 수식의 예시입니다.

8.1. 행렬 역산 (Matrix inversion formula)

  • op를 객체별, ~op를 요소별로 사용하는 경우:
    b = a.I - a.I * u / (c.I + v/a*u) * v / a
    b = a.I - a.I * u * (c.I + v*a.I*u).I * v * a.I
    
  • op를 요소별, ~op를 객체별로 사용하는 경우:
    b = a.I @- a.I @* u @/ (c.I @+ v@/a@*u) @* v @/ a
    b = a.I ~- a.I ~* u ~/ (c.I ~+ v~/a~*u) ~* v ~/ a
    b = a.I (-) a.I (*) u (/) (c.I (+) v(/)a(*)u) (*) v (/) a
    b = a.I [-] a.I [*] u [/] (c.I [+] v[/]a[*]u) [*] v [/] a
    b = a.I <-> a.I <*> u </> (c.I <+> v</>a<*>u) <*> v </> a
    b = a.I {-} a.I {*} u {/} (c.I {+} v{/}a{*}u) {*} v {/} a
    
  • 관찰: 선형 대수 (linear algebra)에서 op를 객체별로 사용하는 것이 더 좋습니다.
  • 관찰: 복잡한 수식에서 ~op 유형 연산자가 (op) 유형보다 더 보기 좋습니다.

  • 명명된 연산자 (named operators)를 사용하는 경우:
    b = a.I @sub a.I @mul u @div (c.I @add v @div a @mul u) @mul v @div a
    b = a.I ~sub a.I ~mul u ~div (c.I ~add v ~div a ~mul u) ~mul v ~div a
    
  • 관찰: 명명된 연산자는 수학 수식에 적합하지 않습니다.

8.2. 3D 그래프 플로팅 (Plotting a 3d graph)

  • op를 객체별, ~op를 요소별로 사용하는 경우:
    z = sin(x~**2 ~+ y~**2); plot(x,y,z)
    
  • op를 요소별, ~op를 객체별로 사용하는 경우:
    z = sin(x**2 + y**2); plot(x,y,z)
    
  • 관찰: 브로드캐스팅 (broadcasting)이 적용된 요소별 연산은 MatLab보다 훨씬 효율적인 구현을 가능하게 합니다.
  • 관찰: op~op의 의미론이 서로 바뀐 두 개의 관련 클래스를 갖는 것이 유용합니다. 이를 사용하면 ~op 연산자는 다른 형태가 지배적인 코드 덩어리에서만 나타나면 되고, 코드의 일관된 의미론을 유지할 수 있습니다.

  • 자동 브로드캐스팅이 적용된 +- 사용:
    a = b - c; d = a.T*a
    
  • 관찰: bc 중 하나가 행 벡터 (row vector)이고 다른 하나가 열 벡터 (column vector)인 경우, 이는 추적하기 어려운 버그를 소리 없이 생성할 수 있습니다.

9. 기타 문제 (Miscellaneous issues)

9.1. ~+ ~- 연산자의 필요성

객체별 + -는 선형 대수 (linear algebra)에 따른 중요한 건전성 검사 (sanity checks)를 제공하므로 중요합니다. 요소별 + -는 애플리케이션에서 매우 효율적인 브로드캐스팅 (broadcasting)을 허용하므로 중요합니다.

9.2. 좌측 나눗셈 (Left division / solve)

행렬의 경우, a*xx*a와 반드시 같지는 않습니다. a*x==b의 해 (Solution), 즉 x=solve(a,b)x*a==b의 해, 즉 x=div(b,a)와 다릅니다. solve에 대한 새로운 기호를 찾는 것에 대한 논의가 있었습니다. (배경: MatLab은 div(b,a)b/a를, solve(a,b)a\b를 사용합니다.)

파이썬은 새로운 기호 없이 더 나은 해결책을 제공한다는 점이 인식되었습니다. 즉, 역 메서드 .I를 지연시켜 a.I*bb*a.I가 MatLab의 a\bb/a와 동등하도록 만들 수 있습니다. 구현은 매우 간단하며 결과적인 애플리케이션 코드는 깔끔합니다.

9.3. 거듭제곱 연산자 (Power operator)

파이썬이 a**bpow(a,b)로 사용하는 데에는 두 가지 단점이 인식되었습니다.

  • 대부분의 수학자는 이 목적으로 a^b에 더 익숙합니다.
  • ~**=와 같은 긴 증강 할당 연산자가 발생합니다. 그러나 이 문제는 여기의 주요 문제와는 별개입니다.

9.4. 추가 곱셈 연산자 (Additional multiplication operators)

(다중) 선형 대수학에서는 여러 형태의 곱셈이 사용됩니다. 대부분은 선형 대수학적 의미의 곱셈의 변형 (예: 크로네커 곱(Kronecker product))으로 볼 수 있습니다. 그러나 외적 (outer product)과 내적 (inner product)이라는 두 가지 형태가 더 근본적인 것으로 보입니다. 그러나 이들의 사양에는 다음 중 하나일 수 있는 인덱스 (indices)가 포함됩니다.

  • 연산자와 연관되거나,
  • 객체와 연관됩니다.

후자 (아인슈타인 표기법 (Einstein notation))는 종이에서 광범위하게 사용되며, 구현하기도 더 쉽습니다. 인덱스가 있는 텐서 클래스 (tensor-with-indices class)를 구현함으로써, 곱셈의 일반적인 형태는 외적과 내적을 모두 포함하고, 선형 대수 곱셈으로도 특수화될 것입니다. 인덱스 규칙은 다음과 같은 클래스 메서드로 정의될 수 있습니다.

a = b.i(1,2,-1,-2) * c.i(4,-2,3,-1) # a_ijkl = b_ijmn c_lnkm

따라서 하나의 객체별 곱셈으로 충분합니다.

9.5. 비트wise 연산자 (Bitwise operators)

제안된 새로운 수학 연산자들은 비트wise not 연산자인 ~ 기호를 사용합니다. 이는 호환성 문제를 일으키지 않지만 구현을 다소 복잡하게 만듭니다. ^ 기호는 비트wise xor보다 pow에 더 잘 사용될 수 있습니다. 그러나 이는 비트wise 연산자의 미래에 달려 있습니다. 제안된 수학 연산자에는 즉각적인 영향을 미치지 않습니다. | 기호는 행렬 해결 (matrix solve)에 사용될 것을 제안 받았지만, 지연된 .I를 사용하는 새로운 해결책이 여러 면에서 더 낫습니다. 현재 제안은 특수 비트wise 연산자의 필요성을 없앨 더 크고 일반적인 확장에 적합합니다. (아래 “일반적인 요소화에 미치는 영향” 참조)

9.6. 정의에 사용된 특수 연산자 이름에 대한 대안

def __add__(a, b) 대신 def "+"(a, b)

이는 더 큰 문법적 변경을 요구하는 것으로 보이며, 임의의 추가 연산자가 허용될 때만 유용할 것입니다.

10. 일반적인 요소화에 미치는 영향 (Impact on general elementization)

객체별 연산과 요소별 연산의 구분은 객체가 개념적으로 요소들의 컬렉션으로 간주될 수 있는 다른 컨텍스트에서도 의미가 있습니다. 현재 제안이 가능한 미래 확장을 배제하지 않는 것이 중요합니다.

하나의 일반적인 미래 확장은 ~를 주어진 연산자를 요소화하는 메타 연산자로 사용하는 것입니다. 몇 가지 예는 다음과 같습니다.

10.1. 비트wise 연산자 (Bitwise operators)

현재 파이썬은 비트wise 연산에 여섯 가지 연산자 (and (&), or (|), xor (^), complement (~), left shift (<<), right shift (>>))를 할당하며, 각각의 우선순위 수준이 있습니다. 이 중에서 & | ^ ~ 연산자는 정수를 비트 문자열로 간주하여 적용되는 격자 연산자 (lattice operators)의 요소별 버전으로 간주될 수 있습니다.

5 & 6 # 4  (Binary: 101 & 110 = 100)
5 | 6 # 7  (Binary: 101 | 110 = 111)
5 ^ 6 # 3  (Binary: 101 ^ 110 = 011)
~5    # -6 (Binary: ~000...101 = 111...010)

제안된 ~ 연산자를 사용한다면 (예시들은 제안된 의미론을 나타내기 위함이며 실제 Python 코드 아님):

# 5 and 6 # 6 (이것은 논리 and, 비트wise & 5 & 6 은 4)
# 5 or 6 # 5  (이것은 논리 or, 비트wise | 5 | 6 은 7)
5 ~& 6 # 4  (제안된 요소별 비트wise and)
5 ~| 6 # 7  (제안된 요소별 비트wise or)

이들은 정수 내 비트에 국한되지 않고 일반적인 요소별 격자 연산자로 간주될 수 있습니다. xor에 대한 명명된 연산자 ~xor를 가지려면 xor를 예약어 (reserved word)로 만들어야 합니다.

10.2. 리스트 산술 (List arithmetics)

[1, 2] + [3, 4]   # [1, 2, 3, 4] (리스트 연결)
[1, 2] ~+ [3, 4]  # [4, 6] (요소별 덧셈)
['a', 'b'] * 2   # ['a', 'b', 'a', 'b']
'ab' * 2         # 'abab'
['a', 'b'] ~* 2  # ['aa', 'bb'] (요소별 반복)
[1, 2] ~* 2      # [2, 4] (요소별 곱셈)

또한 카르테시안 곱 (Cartesian product)에도 일관적입니다.

# [1,2]*[3,4] # [(1,3),(1,4),(2,3),(2,4)]

10.3. 리스트 컴프리헨션 (List comprehension)

a = [1, 2]; b = [3, 4]
~f(a,b) # [f(x,y) for x, y in zip(a,b)]
~f(a*b) # [f(x,y) for x in a for y in b]
a ~+ b  # [x + y for x, y in zip(a,b)]

10.4. 튜플 생성 (Tuple generation - Python 2.0의 zip 함수)

[1, 2, 3], [4, 5, 6] # ([1, 2, 3], [4, 5, 6])
[1, 2, 3]~,[4, 5, 6] # [(1,4), (2, 5), (3,6)]

10.5. map을 대체하는 일반 요소별 메타 문자 ~ 사용 (Using ~ as generic elementwise meta-character to replace map)

~f(a, b)  # map(f, a, b)
~~f(a, b) # map(lambda *x:map(f, *x), a, b)

더 일반적으로:

def ~f(*x): return map(f, *x)
def ~~f(*x): return map(~f, *x)

10.6. 요소별 포맷 연산자 (Elementwise format operator - 브로드캐스팅 포함)

a = [1,2,3,4,5]
print ["%5d "] ~% a
a = [[1,2],[3,4]]
print ["%5d "] ~~% a

10.7. 풍부한 비교 (Rich comparison)

[1, 2, 3] ~< [3, 2, 1]   # [1, 0, 0]
[1, 2, 3] ~== [3, 2, 1]  # [0, 1, 0]

10.8. 풍부한 인덱싱 (Rich indexing)

[a, b, c, d] ~[2, 3, 1] # [c, d, b]

10.9. 튜플 평탄화 (Tuple flattening)

a = (1,2); b = (3,4)
f(~a, ~b) # f(1,2,3,4)

10.10. 복사 연산자 (Copy operator)

a ~= b # a = b.copy()

특정 수준의 딥 카피 (deep copy)도 가능합니다.

a ~~= b # a = b.copy(2)

참고: 이와 유사한 상황이 더 많을 수 있습니다. 이 일반적인 접근 방식은 각각에 대한 여러 분리된 확장 (병렬 및 교차 이터레이션 (parallel and cross iteration), 리스트 컴프리헨션 (list comprehension), 풍부한 비교 (rich comparison) 등) 대신 대부분의 상황에 잘 맞을 것으로 보입니다. 요소별 연산의 의미론은 애플리케이션에 따라 달라집니다. 예를 들어, 행렬의 요소는 리스트의 리스트 관점에서 두 단계 아래에 있습니다. 이는 현재 제안보다 더 근본적인 변화를 요구합니다. 어떤 경우든, 현재 제안은 이러한 성격의 미래 가능성에 부정적인 영향을 미 미치지 않을 것입니다.

이 섹션은 현재 제안과 일관성이 있지만 추가적인 호환성 또는 기타 문제를 제시할 수 있는 미래 확장 유형을 설명합니다. 이들은 현재 제안과 직접적으로 연결되어 있지 않습니다.

11. 명명된 연산자에 미치는 영향 (Impact on named operators)

논의를 통해 중위 연산자가 수치 계산뿐만 아니라 다른 분야에서도 파이썬에서 희소한 자원임이 일반적으로 분명해졌습니다. 명명된 함수 (named functions)와 유사한 방식으로 중위 연산자를 도입할 수 있는 여러 제안과 아이디어가 제시되었습니다. 현재 확장이 이와 관련하여 미래 확장에 부정적인 영향을 미치지 않음을 보여줍니다.

11.1. 명명된 중위 연산자 (Named infix operators)

@와 같은 메타 문자를 선택하여, 모든 식별자 opname에 대해 @opname 조합이 이항 중위 연산자가 되고 a @opname b == opname(a,b)가 되도록 합니다.

언급된 다른 표현 방식은 다음과 같습니다. .name, ~name~, :name:, (.name), %name% 및 유사한 변형들. 순수 괄호 기반 연산자는 이런 방식으로 사용될 수 없습니다.

이는 @opname를 인식하고 함수 호출과 동일한 구조로 파싱하기 위해 파서 (parser)에 대한 변경을 요구합니다. 이 모든 연산자의 우선순위는 한 수준으로 고정되어야 하므로, 기존 수학 연산자의 우선순위를 유지하는 추가 수학 연산자와는 구현이 다를 것입니다.

현재 제안된 확장은 이러한 형태의 가능한 미래 확장을 어떤 식으로든 제한하지 않습니다.

11.2. 더 일반적인 기호 연산자 (More general symbolic operators)

미래 확장의 추가적인 형태는 메타 문자와 연산자 기호 (연산자 외의 문법 구조에는 사용될 수 없는 기호)를 사용하는 것입니다. @가 메타 문자라고 가정하면:

a + b, a @+ b, a @@+ b, a @+- b

모두 우선순위 계층을 가진 연산자가 될 것이며, 다음과 같이 정의됩니다.

def "+"(a, b)
def "@+"(a, b)
def "@@+"(a, b)
def "@+-"(a, b)

명명된 연산자에 비해 메타 문자 또는 일반 연산자 기호를 기반으로 하는 우선순위에 대한 유연성이 더 크다는 장점이 있습니다. 이는 또한 연산자 구성 (operator composition)도 허용합니다. 단점은 “라인 노이즈” (line noise)와 더 비슷하다는 것입니다. 어떤 경우든 현재 제안은 미래 가능성에 영향을 미치지 않습니다.

이러한 종류의 미래 확장은 유니코드 (Unicode)가 일반적으로 사용 가능해지면 필요하지 않을 수도 있습니다.

이 섹션은 제안된 확장이 가능한 미래 확장과의 호환성을 논의합니다. 이러한 다른 확장 자체의 바람직성 또는 호환성은 여기에서 특별히 고려되지 않습니다.

12. 감사의 말 및 아카이브 (Credits and archives)

논의는 주로 2000년 7월부터 8월까지 comp.lang.python 뉴스 그룹과 python-dev 메일링 리스트에서 이루어졌습니다. 총 수백 개의 게시물이 있었으며, 대부분 다음 두 페이지에서 검색어 “operator”를 통해 검색할 수 있습니다.

이곳에 언급하기에는 너무 많은 기여자들의 이름이 있으며, 여기서 논의된 아이디어의 상당 부분이 우리의 것이 아님을 밝힙니다.

논의를 탐색하는 데 도움이 될 수 있는 몇 가지 주요 게시물 (작성자들의 관점)이 나열되어 있습니다.

이 PEP의 초기 초안은 다음 링크에서 확인할 수 있습니다.

Greg Wilson의 “Adding New Linear Algebra Operators to Python”이라는 제목의 대안 PEP (공식적으로 PEP 211)도 있습니다. 첫 번째 (그리고 현재) 버전은 다음 링크에서 확인할 수 있습니다.

13. 추가 참고 자료 (Additional References)

http://MatPy.sourceforge.net/Misc/index.html


최종 수정일: 2024-04-14 20:08:31 GMT

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

Comments