PEP 468 – 함수에서 **kwargs의 순서 보존하기
- Author:
- Eric Snow <ericsnowcurrently at gmail.com>
- Discussions-To:
- Python-Ideas list
- Status:
- Final
- Type:
- Standards Track
- Created:
- 05-Apr-2014
- Python-Version:
- 3.6
- Post-History:
- 05-Apr-2014, 08-Sep-2016
- Resolution:
- Python-Dev message
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
함수 정의에서 **kwargs 문법은 인터프리터가 다른 이름 있는 매개변수에 대응하지 않는 모든 키워드 인자를 수집해야 함을 나타냅니다. 그러나 Python은 수집된 키워드 인자가 함수에 전달된 순서를 보존하지 않습니다. 어떤 상황에서는 순서가 중요합니다. 이 PEP는 수집된 키워드 인자를 함수 본문에서 순서 있는 매핑으로 노출하도록 규정합니다.
동기
함수 정의에서 Python의 **kwargs 문법은 키워드 인자를 동적으로 처리하는 강력한 수단을 제공합니다. 이 문법의 일부 활용(Use Cases를 참고하십시오)에서는 수집된 키워드 인자에 적용되는 의미상 순서가 보존되어야 합니다. 당연하게도, 이는 OrderedDict가 dict와 관련된 방식과 비슷합니다.
현재 순서를 보존하려면 실제 함수 호출과는 별도로 수동으로 처리해야 합니다. 이를 위해서는 OrderedDict든 2-튜플의 이터러블이든 순서 있는 매핑을 만든 다음, 이를 함수에 단일 인자로 전달해야 합니다. [1]
이 PEP에서 설명하는 기능이 있으면 그러한 상용구 코드는 더 이상 필요하지 않습니다.
비교를 위해 보이면, 현재는 다음과 같고:
kwargs = OrderedDict()
kwargs['eggs'] = ...
...
def spam(a, kwargs):
...
이 제안을 적용하면 다음과 같습니다:
def spam(a, **kwargs):
...
Alyssa (Nick) Coghlan은 몇 가지 사용 사례에 대해 이야기하면서 이를 잘 요약했습니다 [2]:
These *can* all be done today, but *not* by using keyword arguments.
In my view, the problem to be addressed is that keyword arguments
*look* like they should work for these cases, because they have a
definite order in the source code. The only reason they don't work
is because the interpreter throws that ordering information away.
It's a textbook case of a language feature becoming an attractive
nuisance in some circumstances: the simple and obvious solution for
the above use cases *doesn't actually work* for reasons that aren't
obviously clear if you don't have a firm grasp of Python's admittedly
complicated argument handling.
이러한 관찰은 수년에 걸쳐 이 제안이 거듭 등장했다는 점과 사람들이 OrderedDict의 생성자 때문에 수없이 혼란을 겪었다는 점으로 뒷받침됩니다. [3] [4] [5]
사용 사례
Alyssa가 언급했듯이, 순서가 중요하리라 예상되는 경우에 **kwargs의 현재 동작은 직관적이지 않습니다. 아래에 설명하는 더 구체적인 경우 외에도, 일반적으로 “반복 순서를 제어하는 것 그리고 필드 이름과 값을 설정하는 것을 한 번의 호출로 하고자 하는 그 밖의 모든 경우가 잠재적으로 이득을 볼 것입니다.” [6] 이는 순서 있는 타입의 팩토리(예: __init__())의 경우에 중요합니다.
직렬화
당연히 OrderedDict는 순서 있는 kwargs로부터 이득을 볼 것입니다(__init__()와 update() 모두). 그러나 그 이점은 직렬화 API에도 미칩니다 [2]:
In the context of serialisation, one key lesson we have learned is
that arbitrary ordering is a problem when you want to minimise
spurious diffs, and sorting isn't a simple solution.
Tools like doctest don't tolerate spurious diffs at all, but are
often amenable to a sorting based answer.
The cases where it would be highly desirable to be able use keyword
arguments to control the order of display of a collection of key
value pairs are ones like:
* printing out key:value pairs in CLI output
* mapping semantic names to column order in a CSV
* serialising attributes and elements in particular orders in XML
* serialising map keys in particular orders in human readable formats
like JSON and YAML (particularly when they're going to be placed
under source control)
디버깅
Raymond Hettinger의 말을 빌리면 다음과 같습니다 [7]:
It makes it easier to debug if the arguments show-up in the order
they were created. AFAICT, no purpose is served by scrambling them.
기타 사용 사례
- 모의(mock) 객체. [8]
- 객체 표현 제어.
- 기본값을 지정할 수 있는 대안 namedtuple().
- 순서에 따른 인자 우선순위 지정.
우려 사항
성능
이미 언급했듯이, 순서가 있는 키워드 인자라는 아이디어는 여러 차례 제기되었습니다. 그때마다 같은 답변이 돌아왔습니다. 키워드 인자의 순서를 보존하면 함수 호출 성능이 상당히 나빠지므로 그럴 만한 가치가 없다는 것이었습니다. 그러나 Guido는 다음과 같이 언급했습니다 [9]:
Making **kwds ordered is still open, but requires careful design and
implementation to avoid slowing down function calls that don't benefit.
아래에서 언급하겠지만, 복잡성이 늘어나는 대가를 치르면 이를 우회하는 방법들이 있습니다. 결국 가장 단순한 접근 방식이 가장 합리적입니다. 바로 수집된 키워드 인자를 OrderedDict에 담는 것입니다. 그러나 OrderedDict의 C 구현이 없으면 논의할 거리가 많지 않습니다. 이는 Python 3.5에서 바뀌었습니다. [10]
참고: Python 3.6에서 dict는 순서를 보존합니다. 이로써 성능에 대한 우려는 사실상 사라집니다.
다른 Python 구현
고려해야 할 또 다른 중요한 문제는 새로운 기능이 여러 Python 구현을 염두에 두어야 한다는 점입니다. 언젠가는 각 구현이 순서가 있는 kwargs를 구현할 것으로 예상됩니다. 이 점에서 이 아이디어에는 문제가 없어 보입니다. [11] 주요 Python 구현들을 대상으로 한 비공식 조사에 따르면 이 기능은 큰 부담이 되지 않을 것으로 나타났습니다.
명세
버전 3.6부터 Python은 함수에 전달된 키워드 인자의 순서를 보존합니다. 이를 위해 수집된 kwargs는 이제 순서가 있는 매핑이 됩니다. 이것이 반드시 OrderedDict를 의미하지는 않는다는 점에 유의하십시오. CPython 3.6의 dict는 이제 PyPy와 마찬가지로 순서가 있습니다.
이는 달리 지정되지 않은 키워드 인자를 수집하기 위해 정의에서 **kwargs 문법을 사용하는 함수에만 적용됩니다. 그러한 키워드 인자의 순서만 보존됩니다.
**-언패킹 문법과의 관계
함수 호출의 ** 언패킹 문법은 이 제안과 특별한 연결이 없습니다. 언패킹으로 제공된 키워드 인자는 지금과 정확히 같은 방식으로 처리됩니다. 정의된 매개변수와 일치하는 인자는 해당 매개변수에 모이고, 나머지는 (일치하지 않는 다른 키워드 인자와 마찬가지로) 순서가 있는 kwargs에 수집됩니다.
dict처럼 순서가 정의되지 않은 매핑을 언패킹하면 평소처럼 그 순회 순서가 보존된다는 점에 유의하십시오. 다만 그 순서는 여전히 정의되지 않은 상태로 남습니다. 언패킹된 키-값 쌍이 다시 담기는 순서가 있는 매핑은 다른 순서를 제공할 수 없습니다. 이는 놀라운 일이 아닐 것입니다.
이러한 매핑을 언패킹했다가 다시 패킹하지 않고 함수의 kwargs로 그대로 전달하는 방안에 대해 짧은 논의가 있었지만, 이는 이 제안의 범위를 벗어날 뿐만 아니라 어쨌든 좋지 않은 아이디어일 가능성이 높습니다. (그 논의가 짧았던 데에는 이유가 있습니다.)
inspect.Signature와의 관계
Signature 객체는 변경할 필요가 없습니다. (Signature.bind()와 Signature.bind_partial()이 반환하는) inspect.BoundArguments의 kwargs 매개변수는 dict에서 OrderedDict로 바뀝니다.
C-API
변경하지 않음.
문법
이 제안으로 추가되거나 변경되는 문법은 없습니다.
하위 호환성
다음 사항이 변경됩니다.
- 이제 kwargs의 반복 순서가 일관됩니다(물론 앞서 설명한 경우는 제외합니다)
참조 구현
CPython에서는 따로 할 작업이 없습니다.
대안
옵트아웃 데코레이터
이는 현재 제안과 동일하지만, 한 가지 예외로 Python은 수집된 키워드 인자를 OrderedDict 대신 일반 dict에 담도록 하는 데코레이터도 functools에 제공합니다.
전망:
이 방식은 일부 드문 경우에 성능이 크게 달라지는 것으로 확인되거나, 다른 방법으로는 해결할 수 없는 하위 호환성 문제가 있을 때만 필요합니다.
옵트인 데코레이터
현재 상태는 변경되지 않습니다. 대신 Python은 functools에 데코레이터를 제공하며, 이 데코레이터는 데코레이트된 함수를 순서가 있는 키워드 인자를 받아야 하는 함수로 등록하거나 표시합니다. 호출 시점에 함수를 검사하는 데 드는 성능 오버헤드는 미미합니다.
전망:
유일한 실질적인 단점은 래퍼 정의에서 kwargs를 사용하고 래핑된 함수를 호출할 때 kwargs 언패킹을 사용하여 키워드 인자를 완벽하게 보존하려는 함수 래퍼 팩토리(예: functools.partial 및 많은 데코레이터)의 경우입니다. 각 래퍼를 개별적으로 수정해야 하지만, functools.wraps()가 이를 자동으로 처리하게 하면 도움이 됩니다.
__kworder__
키워드 인자의 순서는 호출 시점에 별도의 리스트에 저장됩니다. 이 리스트는 함수 로컬에서 __kworder__에 바인딩됩니다.
전망:
이 방식 역시 래퍼의 경우를 복잡하게 만듭니다.
더 빠른 반복을 제공하는 컴팩트 dict
Raymond Hettinger는 dict에서 (첫 번째 삭제가 일어나기 전까지) 삽입 순서를 보존하게 되는 dict 구현 아이디어를 제안했습니다. 이는 kwargs에 완벽하게 들어맞습니다. [5]
전망:
이 아이디어는 실현 가능성과 일정 모두 아직 불확실합니다.
참고로 이제 Python 3.6에는 이 dict 구현이 포함되어 있습니다.
***kwargs
이는 함수 시그니처에 **kwargs와 상호 배타적인 병렬 형태로 새로운 형식을 추가하는 것입니다. 새 문법인 ***kwargs(별표가 세 개라는 점에 유의하십시오)는 kwargs가 키워드 인자의 순서를 보존해야 함을 나타냅니다.
전망:
새 문법은 가장 절박한 상황에서만 Python에 추가됩니다. 다른 해결책이 있는 이상 새 문법은 정당화될 수 없습니다. 게다가 모든 옵트인 방식의 해결책과 마찬가지로, 새 문법은 전달(pass-through) 사례를 복잡하게 만듭니다.
어노테이션
이는 데코레이터 방식의 변형입니다. 데코레이터로 함수를 표시하는 대신 **kwargs에 함수 어노테이션을 사용합니다.
전망:
전달 사례의 복잡성 외에도, Python 코어 개발에서는 어노테이션 사용을 적극적으로 권장하지 않아 왔습니다. 순서 보존을 옵트인하는 데 어노테이션을 사용하면 애플리케이션 수준에서 어노테이션을 다른 용도로 사용하는 것과 충돌할 위험이 있습니다.
dict.__order__
dict 객체에 새 속성 __order__가 추가됩니다. 이 속성의 기본값은 None이며, kwargs의 경우 인터프리터는 위에서 __kworder__에 대해 설명한 것과 같은 방식으로 이 속성을 사용합니다.
전망:
kwargs 성능에는 전혀 영향을 주지 않지만, 변경이 상당히 침습적입니다(Python은 dict를 매우 많이 사용합니다). 또한 래퍼 사례에서는 인터프리터가 __order__를 주의해서 보존해야 합니다.
KWArgsDict.__order__
이는 dict.__order__ 아이디어와 같지만, kwargs는 __order__ 속성을 제공하는 최소한의 새 dict 서브클래스의 인스턴스가 됩니다. 대신 dict는 변경되지 않습니다.
전망:
단순히 OrderedDict로 전환하는 편이 덜 복잡하고 더 직관적인 변경입니다.
감사의 글
유용한 피드백을 주신 Andrew Barnert와 지난 모든 이메일 스레드의 참여자분들께 감사드립니다.
각주
참고 문헌
https://mail.python.org/pipermail/python-ideas/2010-October/008445.html
https://mail.python.org/pipermail/python-ideas/2011-January/009037.html
https://mail.python.org/pipermail/python-ideas/2013-February/019690.html
https://mail.python.org/pipermail/python-ideas/2013-May/020727.html
https://mail.python.org/pipermail/python-ideas/2014-March/027225.html
http://bugs.python.org/issue16276
http://bugs.python.org/issue16553
https://mail.python.org/pipermail/python-dev/2013-May/126327.html
Copyright
This document has been placed in the public domain.