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

Python 개선 제안 한국어 번역

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

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 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는 순서가 있는 키워드 인자를 사용하면 이점을 얻습니다(__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.

기타 사용 사례

  • 모의 객체입니다. [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 객체는 변경할 필요가 없습니다. inspect.BoundArguments의 kwargs매개변수(Signature.bind() 및 Signature.bind_partial()이 반환)는 dict에서 OrderedDict로 변경됩니다.

C-API

변경 사항이 없습니다.

구문

이 제안으로 구문이 추가되거나 변경되지 않습니다.

하위 호환성

다음 사항이 변경됩니다.

  • kwargs의 반복 순서는 이제 일관됩니다(물론 위에서 설명한 경우는 제외합니다).

참조 구현

CPython에서는 수행할 작업이 없습니다.

대안

옵트아웃 데코레이터

이는 Python이 수집된 키워드 인자를 OrderedDict 대신 일반 dict로 묶도록 하는 데코레이터를 functools에 함께 제공한다는 점을 제외하면 현재 제안과 동일합니다.

전망:

일부 일반적이지 않은 경우에 성능 차이가 상당한 것으로 판단되거나 다른 방법으로 해결할 수 없는 하위 호환성 문제가 있는 경우에만 필요합니다.

옵트인 데코레이터

현재 상태는 변경되지 않습니다. 대신 Python은 functools에 데코레이터를 제공하여, 데코레이터가 적용된 함수를 순서가 지정된 키워드 인자를 받아야 하는 함수로 등록하거나 표시하도록 합니다. 호출 시점에 함수를 확인하는 데 따른 성능 오버헤드는 미미합니다.

전망:

유일하게 실질적인 단점은 래퍼 정의에서 kwargs를 사용하고 래핑된 함수 호출에서 kwargs 압축 해제를 사용하여 키워드 인자를 완벽하게 보존하려는 함수 래퍼 팩토리(예: functools.partial 및 많은 데코레이터)의 경우입니다. 각 래퍼를 별도로 업데이트해야 하지만, functools.wraps()가 이 작업을 자동으로 수행하면 도움이 됩니다.

__kworder__

키워드 인자의 순서는 호출 시점에 별도의 리스트에 저장됩니다. 해당 리스트는 함수 로컬에서 __kworder__에 바인딩됩니다.

전망:

이 방법 역시 래퍼의 경우를 복잡하게 만듭니다.

더 빠른 반복을 지원하는 컴팩트 딕셔너리

Raymond Hettinger는 딕셔너리에서 (첫 번째 삭제가 발생하기 전까지) 삽입 순서를 보존하는 결과를 가져올 dict 구현 아이디어를 제시했습니다. 이는 kwargs에 완벽하게 적합합니다. [5]

전망:

이 아이디어는 실행 가능성과 시기 모두 아직 불확실합니다.

Python 3.6에는 이제 이 dict 구현이 포함되어 있다는 점에 유의하십시오.

***kwargs

이는 함수 시그니처에 **kwargs와 상호 배타적인 병렬 형태로 새로운 형식을 추가합니다. 새로운 구문인 ***kwargs(별표가 세 개라는 점에 유의하십시오)는 kwargs가 키워드 인자의 순서를 보존해야 함을 나타냅니다.

전망:

Python에는 가장 절박한 상황에서만 새로운 구문이 추가됩니다. 다른 사용 가능한 해결책이 있으므로 새로운 구문은 정당화될 수 없습니다. 또한 모든 옵트인 방식의 해결책과 마찬가지로 새로운 구문은 패스스루 경우를 복잡하게 만듭니다.

어노테이션

이는 데코레이터 접근법의 변형입니다. 함수를 표시하기 위해 데코레이터를 사용하는 대신, **kwargs에 함수 어노테이션을 사용합니다.

전망:

패스스루의 복잡성뿐만 아니라, Python 코어 개발에서는 어노테이션 사용을 적극적으로 권장하지 않았습니다. 순서 보존을 옵트인하기 위해 어노테이션을 사용하면 애플리케이션 수준에서 어노테이션을 다른 용도로 사용하는 것과 충돌할 위험이 있습니다.

dict.__order__

dict 객체에는 새 속성 __order__가 추가되며, 이 속성의 기본값은 None입니다. kwargs의 경우 인터프리터는 위에서 __kworder__에 대해 설명한 것과 같은 방식으로 이 속성을 사용합니다.

전망:

kwargs 성능에는 전혀 영향이 없지만 변경 사항이 상당히 침투적일 수 있습니다(Python은 dict를 많이 사용합니다). 또한 래퍼의 경우 인터프리터는 __order__를 보존하도록 주의해야 합니다.

KWArgsDict.__order__

이는 dict.__order__ 아이디어와 동일하지만, kwargs는 __order__ 속성을 제공하는 새로운 최소 dict 서브클래스의 인스턴스가 됩니다. 대신 dict는 변경되지 않습니다.

전망:

간단히 OrderedDict로 전환하는 편이 덜 복잡하고 더 직관적인 변경입니다.

감사의 글

유용한 피드백을 제공해 주신 Andrew Barnert와 지금까지의 모든 이메일 스레드 참가자들께 감사드립니다.

각주

참고 자료