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

Python 개선 제안 한국어 번역

PEP 3113 – 튜플 매개변수 언패킹 제거

Author:
Brett Cannon <brett at python.org>
Status:
Final
Type:
Standards Track
Created:
02-Mar-2007
Python-Version:
3.0
Post-History:


Table of Contents

번역·라이선스 안내

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

초록

튜플 매개변수 언패킹은 함수 시그니처에서 튜플을 매개변수로 사용하여 시퀀스 인자가 자동으로 언패킹되도록 하는 것입니다. 예시는 다음과 같습니다.:

def fxn(a, (b, c), d):
    pass

시그니처에서 (b, c)를 사용하면 함수의 두 번째 인자는 길이가 2인 시퀀스(예: [42, -13])여야 합니다. 이러한 시퀀스가 전달되면 언패킹되어 그 값이 매개변수에 대입되며, 이는 마치 매개변수 안에서 b, c = [42, -13] 문이 실행된 것과 같습니다.

안타깝게도 Python의 풍부한 함수 시그니처 기능 중 이 기능은 일부 상황에서는 편리하지만, 그 가치보다 더 많은 문제를 일으킵니다. 따라서 이 PEP는 Python 3.0에서 이 기능을 언어에서 제거할 것을 제안합니다.

제거해야 하는 이유

인트로스펙션 문제

Python은 매우 강력한 인트로스펙션 기능을 갖추고 있습니다. 이 기능은 함수 시그니처에도 적용됩니다. 함수의 호출 시그니처가 무엇인지에 관해 숨겨진 세부 사항은 없습니다. 일반적으로 함수 객체와 그 객체의 여러 속성(함수의 func_code 속성 포함)을 살펴보면 함수 시그니처에 관한 여러 세부 사항을 상당히 쉽게 알아낼 수 있습니다.

그러나 튜플 매개변수에 관해서는 큰 어려움이 있습니다. 튜플 매개변수의 존재는 함수 코드 객체의 co_varnames 속성에서 그 이름이 .과 숫자로 이루어져 있다는 것으로 표시됩니다. 이 방식에서는 튜플 인자가 바이트코드만 알고 있고 Python 소스에서는 입력할 수 없는 이름에 바인딩됩니다. 그러나 이것만으로는 튜플의 형식, 즉 길이나 중첩된 튜플이 있는지 여부 등을 알 수 없습니다.

함수로부터 튜플에 관한 모든 세부 사항을 얻으려면 함수의 바이트코드를 분석해야 합니다. 함수의 첫 번째 바이트코드가 말 그대로 튜플 인자를 언패킹하는 동작에 해당하기 때문입니다. 튜플 매개변수의 이름이 .1이고 변수 spam과 monty로 언패킹될 것으로 예상된다고 가정하면(즉, 튜플이 (spam, monty)라는 뜻입니다), 함수의 첫 번째 바이트코드는 spam, monty = .1 문에 해당합니다. 따라서 튜플 매개변수의 모든 세부 사항을 알려면 함수의 초기 바이트코드를 살펴보고 \.\d+ 형식의 매개변수에 대한 튜플 언패킹을 감지하여, 예상되는 인자에 관한 모든 정보를 추론해야 합니다. inspect.getargspec 함수가 튜플 매개변수에 관한 정보를 제공할 수 있는 것도 바이트코드 분석 덕분입니다. 이는 쉬운 일이 아니며, 인트로스펙션 도구가 Python 바이트코드의 동작 방식을 알아야 하므로 부담이 됩니다(다른 모든 종류의 매개변수는 Python 바이트코드에 대한 지식을 요구하지 않으므로, 이는 원래라면 불필요한 부담입니다).

바이트코드 분석의 어려움은 차치하더라도, Python 바이트코드 사용에 대한 의존성에는 또 다른 문제가 있습니다. IronPython [3]은 파이썬의 바이트코드를 사용하지 않습니다. IronPython은 .NET 프레임워크를 기반으로 하므로, 그 대신 함수의 func_code.co_code 속성에 MSIL [4]을 저장합니다. 이 때문에 IronPython에서 실행할 때 inspect.getargspec 함수가 동작하지 않습니다. 다른 Python 구현이 영향을 받는지는 알려져 있지 않지만, 해당 구현이 단순히 Python 가상 머신을 재구현한 것이 아니라면 영향을 받는다고 가정하는 것이 합리적입니다.

제거해도 기능 손실 없음

Introspection Issues에서 언급했듯이, 튜플 매개변수를 처리하기 위해 함수의 바이트코드는 인자를 적절한 매개변수 이름으로 언패킹하는 데 필요한 바이트코드로 시작합니다. 이는 튜플 매개변수를 구현하는 데 특별한 지원이 필요하지 않으며, 따라서 이를 제거하더라도 기능 손실은 없고 편의성이 줄어들 수 있을 뿐이라는 뜻입니다(이에 관해서는 Why They Should (Supposedly) Stay에서 다룹니다).

이 PEP의 첫머리에 나온 예제 함수는 다음과 같이 쉽게 다시 작성할 수 있으며:

def fxn(a, b_c, d):
    b, c = b_c
    pass

그렇게 해도 기능이 전혀 손실되지 않습니다.

규칙에 대한 예외

Python 함수가 가질 수 있는 여러 종류의 매개변수를 살펴보면, 튜플 매개변수는 규칙이라기보다 예외에 가깝다는 것을 알 수 있습니다.

PEP 3102 (키워드 전용 인자)와 PEP 3107 (함수 어노테이션)을 생각해 보십시오. 두 PEP 모두 채택되었으며 함수 시그니처에 새로운 기능을 도입합니다. 그런데도 두 PEP 모두 그 새로운 기능을 튜플 매개변수 전체에는 적용할 수 없습니다. PEP 3102는 튜플 매개변수를 전혀 지원하지 않습니다(튜플 매개변수를 이름으로 참조할 방법이 없으므로 이는 당연합니다). PEP 3107은 튜플 안의 각 항목에 대한 어노테이션은 허용하지만(예: (x:int, y:int)), 튜플 전체에 대한 어노테이션은 허용하지 않습니다(예: (x, y):int).

또한 튜플 매개변수가 존재하기 때문에 함수 시그니처에서 시퀀스 객체가 매핑 객체와 따로 취급됩니다. 매핑 객체(예: dict)를 매개변수로 전달하여 시퀀스가 튜플 매개변수로 언패킹되는 것과 같은 방식으로 언패킹되게 할 방법은 없습니다.

정보가 부족한 오류 메시지

다음 함수를 생각해 보십시오:

def fxn((a, b), (c, d)):
    pass

fxn(1, (2, 3))으로 호출하면 TypeError: unpack non-sequence라는 오류 메시지가 표시됩니다. 이 오류 메시지는 어느 튜플이 제대로 언패킹되지 않았는지 전혀 알려 주지 않습니다. 또한 이것이 인자 때문에 발생한 결과라는 표시도 없습니다. 함수 인자에 관한 다른 오류 메시지들은 시그니처와의 관계를 명시적으로 밝힙니다. TypeError: fxn() takes exactly 2 arguments (0 given) 등이 그 예입니다.

적은 사용 빈도

제가 개인적으로 알거나 PyCon 2007 스프린트에서 만난 몇 안 되는 Python 프로그래머를 대상으로 비공식적으로 조사해 보니 대다수는 이 기능을 알지 못하고 나머지는 그냥 사용하지 않는다는 결과가 나왔지만, 이 기능이 많이 사용되지 않는다는 주장을 뒷받침하려면 확실한 수치가 필요합니다.

함수와 메서드 정의를 찾기 위해 정규 표현식 ^\s*def\s*\w+\s*\(를 사용하여 Python 코드 저장소의 Lib/ 디렉터리에 있는 모든 줄을 순회한 결과, 트렁크에서 22,252개가 일치했습니다.

튜플 매개변수를 포함하는 def 문을 찾기 위해 .*,\s*\(를 덧붙이자 일치하는 항목은 41개뿐이었습니다. 이는 def 문 중 튜플 매개변수를 사용하는 것으로 보이는 것이 0.18%에 불과하다는 뜻입니다.

튜플 매개변수를 유지해야 하는 (것으로 주장되는) 이유

실용적 용도

특정한 경우에는 튜플 매개변수가 유용할 수 있습니다. 흔한 예로 데카르트 좌표의 점을 나타내는 두 항목짜리 튜플을 받는 코드가 있습니다. x와 y 좌표를 알아서 언패킹해 주는 것이 편리하다는 점은 사실이지만, 그 정도의 작은 실용적 유용성보다 튜플 매개변수와 관련된 다른 문제가 훨씬 더 크다는 것이 이 PEP의 인자입니다. 그리고 No Loss of Abilities If Removed에서 보였듯이, 튜플 매개변수의 용도는 순전히 편의를 위한 것이며, 다른 방법으로 아주 쉽게 처리할 수 없는 고유한 능력을 제공하지는 않습니다.

매개변수의 자기 문서화

튜플 매개변수가 특정 시퀀스 형식이어야 하는 매개변수를 스스로 문서화하는 수단이 된다는 주장이 있었습니다. Practical Use의 데카르트 좌표 점 예제를 사용하면, 함수에서 (x, y)를 매개변수로 보는 것만으로도 해당 매개변수의 인자로 길이가 2인 튜플이 필요하다는 것이 분명해집니다.

그러나 Python은 매개변수의 용도를 문서화하는 여러 다른 방법을 제공합니다. 문서화 문자열은 어떤 인자가 기대되는지 설명하는 데 필요한 정보를 충분히 제공하기 위한 것입니다. 튜플 매개변수는 시퀀스 인자의 기대 길이는 알려 줄 수 있지만, 그 데이터가 어디에 쓰일지는 알려 주지 않습니다. 또한 모든 매개변수가 튜플 매개변수인 것이 아니라면, 다른 어떤 인자가 기대되는지 알기 위해 독스트링도 읽어야 합니다.

함수 어노테이션(튜플 매개변수에는 사용할 수 없습니다)도 문서화 역할을 할 수 있습니다. 어노테이션은 어떤 형태든 될 수 있으므로, 예전에 튜플 매개변수였던 것은 tuple, tuple(2), Cartesian point, (x, y) 등의 어노테이션이 달린 단일 인자 매개변수가 될 수 있습니다. 어노테이션은 특정 길이의 시퀀스여야 한다는 점을 포함하여, 매개변수에 어떤 인자가 기대되는지를 문서화하는 데 큰 유연성을 제공합니다.

전환 계획

튜플 매개변수가 제거된 3.x로 Python 2.x 코드를 이전하기 위해 두 단계를 제안합니다. 첫째, Python 2.6에서 Python 컴파일러가 튜플 매개변수를 만나면 적절한 경고를 내보내야 합니다. 이는 Python 2.6과 비교하여 Python 3.0에서 일어날 다른 모든 문법 변경과 마찬가지로 취급됩니다.

둘째, 2to3 리팩터링 도구 [1]에 튜플 매개변수를 함수의 첫 번째 문장에서 언패킹되는 단일 매개변수로 변환하는 픽서 [2]가 추가됩니다. 새 매개변수의 이름은 바뀝니다. 그런 다음 새 매개변수는 원래 튜플 매개변수에서 쓰이던 이름들로 언패킹됩니다. 즉, 다음 함수는:

def fxn((a, (b, c))):
    pass

다음과 같이 변환됩니다:

def fxn(a_b_c):
    (a, (b, c)) = a_b_c
    pass

람다는 단일 표현식이라는 제약 때문에 튜플 매개변수를 사용하므로, 람다에서도 이를 지원해야 합니다. 이는 기대되는 시퀀스 인자를 단일 매개변수에 바인딩한 다음 그 매개변수에 인덱싱하는 방식으로 이루어집니다:

lambda (x, y): x + y

다음과 같이 변환됩니다:

lambda x_y: x_y[0] + x_y[1]

참고 문헌