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

Python 개선 제안 한국어 번역

PEP 448 – 추가적인 언패킹 일반화

Author:
Joshua Landau <joshua at landau.ws>
Discussions-To:
Python-Ideas list
Status:
Final
Type:
Standards Track
Created:
29-Jun-2013
Python-Version:
3.5
Post-History:


Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 * 이터러블 언패킹 연산자와 ** 딕셔너리 언패킹 연산자의 사용 범위를 확장하여, 더 많은 위치에서, 횟수에 관계없이, 그리고 함수 호출과 디스플레이에서 언패킹할 수 있도록 하자고 제안합니다.

함수 호출에서 언패킹을 한 번만이 아니라 횟수에 관계없이 할 수 있도록 제안합니다.:

>>> print(*[1], *[2], 3)
1 2 3
>>> dict(**{'x': 1}, y=2, **{'z': 3})
{'x': 1, 'y': 2, 'z': 3}

튜플, 리스트, 세트, 딕셔너리 디스플레이 안에서 언패킹을 허용하도록 제안합니다.:

>>> *range(4), 4
(0, 1, 2, 3, 4)
>>> [*range(4), 4]
[0, 1, 2, 3, 4]
>>> {*range(4), 4}
{0, 1, 2, 3, 4}
>>> {'x': 1, **{'y': 2}}
{'x': 1, 'y': 2}

딕셔너리에서는 항상 나중 값이 앞선 값을 덮어씁니다.:

>>> {'x': 1, **{'x': 2}}
{'x': 2}

>>> {**{'x': 2}, 'x': 1}
{'x': 1}

이 PEP는 리스트, 세트, 딕셔너리 컴프리헨션 안의 언패킹 연산자는 다루지 않지만, 향후 제안에서 이를 다룰 가능성을 배제하지는 않습니다.

근거

현재 * 이터러블 언패킹 연산자의 사용에는 가독성을 해칠 수 있는 불필요한 제약이 있습니다.

여러 번 언패킹하는 기능의 근거는 분명합니다. 여러 이터러블을 함수 정의에 언패킹하거나 언패킹 뒤에 위치 인자를 더 이어서 쓰고 싶을 때, 가장 자연스러운 방법은 다음과 같이 작성하는 것입니다.:

function(**kw_arguments, **more_arguments)

function(*arguments, argument)

이것이 유용한 간단한 예로는 print와 str.format이 있습니다. 그렇게 하는 대신 다음과 같이 작성해야 할 수도 있습니다.:

kwargs = dict(kw_arguments)
kwargs.update(more_arguments)
function(**kwargs)

args = list(arguments)
args.append(arg)
function(*args)

또는 방법을 알고 있다면 다음과 같이 작성할 수 있습니다.:

from collections import ChainMap
function(**ChainMap(more_arguments, arguments))

from itertools import chain
function(*chain(args, [arg]))

이러한 방식은 불필요한 코드 잡음을 늘리며, 첫 번째 방법들은 같은 작업을 중복해서 수행하게 만듭니다.

컨테이너 안에서 언패킹을 허용하는 주된 근거는 두 가지입니다. 첫째, 대입의 대칭성이 있습니다. 타입의 세부 사항을 무시하면 fst, *other, lst = elems와 elems = fst, *other, lst는 대략 서로의 역연산입니다. 결과적으로 이는 특수한 경우를 없애 언어를 단순하게 만듭니다.

둘째, 딕셔너리 결합과 같은 여러 종류의 “덧셈”을 크게 단순화하며, 이를 모호함 없이 명확하게 정의된 방식으로 수행합니다.:

combination = {**first_dictionary, "x": 1, "y": 2}

다음과 같이 작성하는 대신에 말입니다.:

combination = first_dictionary.copy()
combination.update({"x": 1, "y": 2})

이는 표현식을 선호하는 맥락에서 특히 중요합니다. 이는 또한 이터러블들을 합쳐 리스트로 만드는 더 읽기 쉬운 방법으로도 유용합니다. 예를 들어 my_list + list(my_tuple) + list(my_range)는 이제 간단히 [*my_list, *my_tuple, *my_range]와 같습니다.

명세

함수 호출은 개수 제한 없이 * 및 ** 언패킹을 받을 수 있습니다. * 언패킹과 위치 인자 사이의 순서에는 아무런 제한이 없으며, ** 언패킹과 키워드 인자 사이의 순서에도 아무런 제한이 없습니다.

함수 호출에서는 키워드 인자가 위치 인자 뒤에 와야 하고, 여기에 더해 ** 언패킹이 * 언패킹 뒤에 와야 한다는 제약이 계속 유지됩니다.

현재는 어떤 인자가 여러 번 주어지면 — 예를 들어 위치 인자가 위치로도, 키워드로도 주어지면 — TypeError가 발생합니다. 이는 여러 개의 ** 언패킹을 통해 중복 인자를 제공하는 경우에도 마찬가지이며(예: f(**{'x': 2}, **{'x': 3})), 다만 이 경우 오류는 런타임에 감지됩니다.

함수는 다음과 같습니다.:

function(
    argument or *args, argument or *args, ...,
    kwargument or *args, kwargument or *args, ...,
    kwargument or **kwargs, kwargument or **kwargs, ...
)

튜플, 리스트, 세트, 딕셔너리에서 언패킹을 사용할 수 있게 됩니다. 이는 함수 호출에서 언패킹할 때와 마찬가지로, 언패킹된 항목의 요소들이 언패킹한 자리에 순서대로 삽입된 것처럼 동작합니다. 딕셔너리에는 ** 언패킹을 사용해야 하고, 나머지에는 모두 * 언패킹을 사용해야 합니다.

딕셔너리의 키는 오른쪽에서 왼쪽으로의 우선순위를 유지하므로, {**{'a': 1}, 'a': 2, **{'a': 3}}은 {'a': 3}으로 평가됩니다. 언패킹의 개수나 위치에는 제한이 없습니다.

단점

함수 호출에서 허용되는 인자 순서가 이전보다 복잡합니다. 이 규칙을 가장 간단하게 설명하면 “위치 인자는 키워드 인자와 ** 언패킹보다 앞에 오고, * 언패킹은 ** 언패킹보다 앞에 옵니다”일 것입니다.

*elements, = iterable은 elements를 리스트로 만들지만, elements = *iterable,은 elements를 튜플로 만듭니다. 이 구문에 익숙하지 않은 사람은 그 이유 때문에 혼란스러울 수 있습니다.

딕셔너리에서는 중복 키가 허용되지만 함수 호출 문법에서는 중복 키가 오류를 일으킨다는 예상치 못한 차이에 대한 우려가 제기되었습니다. 이는 현재 문법에서도 이미 그러하지만, 이 제안은 이 문제를 악화시킬 수 있습니다. 실제로 이것이 얼마나 문제가 될지는 지켜보아야 합니다.

변형안

원래 이 PEP는 함수 호출에서 인자 유형(위치, 키워드, * 또는 **)의 순서를 덜 엄격하게 할 수 있는지 검토했습니다. 이 방안은 지지를 거의 얻지 못해 보류되었습니다.

이 PEP의 초기 버전에서는 리스트, 집합, 딕셔너리 컴프리헨션 안에서 언패킹 연산자를 컨테이너의 이터러블을 평탄화하는 연산자로 사용할 수 있었습니다:

>>> ranges = [range(i) for i in range(5)]
>>> [*item for item in ranges]
[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]

>>> {*item for item in ranges}
{0, 1, 2, 3}

이에 대해서는 가독성에 대한 강한 우려와 약한 지지가 엇갈렸습니다. 이 PEP에서 논란이 덜한 부분까지 불리해지지 않도록, 이 기능은 제안의 나머지 부분과 함께 수락되지 않았습니다.

f(x for x in it)와 같이 함수 호출에서 괄호 없이 쓰는 컴프리헨션은 이미 유효합니다. 이를 다음과 같이 확장할 수 있습니다:

f(*x for x in it) == f((*x for x in it))
f(**x for x in it) == f({**x for x in it})

하지만 이것이 가장 좋은 동작인지, 아니면 f 호출의 인자로 언패킹되어야 하는지가 분명하지 않았습니다. 이 방식은 혼란을 줄 가능성이 높고 유용성도 매우 미미하므로 이 PEP에는 포함하지 않습니다. 대신 이러한 코드는 SyntaxError를 발생시키며, 괄호를 명시한 컴프리헨션을 사용해야 합니다.

승인

이 PEP는 2015년 2월 25일 Guido에 의해 수락되었습니다 [1].

구현

Python 3.5용 구현은 버그 트래커의 Issue 2292에서 찾을 수 있습니다 [2]. 이 구현에는 현재 컴프리헨션 안에서의 언패킹 지원이 포함되어 있으며, 이 부분은 제거해야 합니다.

참고 문헌

[3] Python-ideas 리스트에서의 논의, “리스트 / 배열 컴프리헨션 확장”, Alexander Heger (https://mail.python.org/pipermail/python-ideas/2011-December/013097.html)