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

Python 개선 제안 한국어 번역

PEP 3150 – 문장 로컬 네임스페이스(일명 “given” 절)

Author:
Alyssa Coghlan <ncoghlan at gmail.com>
Status:
Deferred
Type:
Standards Track
Created:
09-Jul-2010
Python-Version:
3.4
Post-History:
14-Jul-2010, 21-Apr-2011, 13-Jun-2011

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 현재 연관된 코드 스위트를 갖지 않는 여러 Python 문에 선택적 given절을 추가할 것을 제안합니다. 이 절은 연관된 문에서 접근할 수 있지만 포함하는 네임스페이스의 일부가 되지는 않는 추가 이름을 위한 문장 로컬 네임스페이스를 생성합니다.

연관된 코드 스위트를 실행하여 생성되는 네임스페이스에 대한 전방 참조를 나타내기 위해 새 기호 ?를 도입할 것을 제안합니다. 이는 types.SimpleNamespace 객체에 대한 참조가 됩니다.

주된 동기는 보다 선언적인 프로그래밍 스타일을 가능하게 하는 것입니다. 이 스타일에서는 수행할 작업을 독자에게 먼저 제시하고, 필요한 하위 계산의 세부 사항을 이어지는 들여쓰기된 스위트에 제시합니다. 대표적인 예로, 이는 일반 대입 문을 classdef문과 동등한 수준으로 끌어올립니다. 여기서는 정의할 항목의 이름이 해당 항목의 값을 계산하는 방법에 대한 세부 사항보다 먼저 독자에게 제시됩니다. 또한 이름이 있는 함수를 “여러 줄 lambda” 방식으로 사용할 수 있게 합니다. 이때 이름은 현재 표현식에서 오직 자리 표시자로만 사용된 다음 이어지는 스위트에서 정의됩니다.

부차적인 동기는 결과 네임스페이스를 오염시키지 않으면서 모듈 및 클래스 수준 코드의 중간 계산을 단순화하는 것입니다.

주어진 절과 지정된 작업을 수행하는 별도의 함수 정의 사이의 관계는 명시적인 while 루프와 해당 while 루프와 동일한 작업 순서를 생성하는 제너레이터 사이의 기존 관계와 유사하게 만드는 것이 목적입니다.

이 PEP의 구체적인 제안은 수년에 걸쳐 이 개념 및 관련 개념을 다양하게 탐구한 결과(예: [1], [2], [3], [6], [8])를 바탕으로 하며, Haskell의 wherelet절에서 어느 정도 영감을 받았습니다. 과거 제안에서 식별된 일부 문제를 피하지만, 아직 구현을 통한 검증을 거치지는 않았습니다.

제안

이 PEP는 표현식을 포함할 수 있거나 순전히 구문상의 목적으로 그러한 문을 대신할 수 있는 단순 문의 구문에 선택적 given절을 추가할 것을 제안합니다. 현재 이 추가의 영향을 받게 될 단순 문의 목록은 다음과 같습니다.

  • 표현식 문
  • 대입 문
  • 증강 대입 문
  • del 문
  • return 문
  • yield 문
  • raise 문
  • assert 문
  • pass 문

given절을 사용하면 헤더 줄에서 하위 표현식을 이름으로 참조하고, 실제 정의는 들여쓰기된 절에서 이어서 작성할 수 있습니다. 간단한 예를 들면:

sorted_data = sorted(data, key=?.sort_key) given:
    def sort_key(item):
        return item.attr1, item.attr2

새로운 기호 ?는 주어진 네임스페이스를 참조하는 데 사용됩니다. 이는 types.SimpleNamespace 인스턴스이므로 ?.sort_keygiven절에서 정의된 이름에 대한 전방 참조로 작동합니다.

주어진 절에는 독스트링을 작성할 수 있으며, 이는 결과 네임스페이스의 __doc__속성으로 연결됩니다.

pass문은 헤더 줄에 의미 있는 표현식을 포함하지 않도록 건너뛰는 일관된 방법을 제공하기 위해 포함됩니다. 이는 의도된 사용 사례는 아니지만, pass자체를 허용하지 않더라도 ...()와 같은 여러 대안이 여전히 존재하므로 이를 방지할 수 없습니다.

given 절의 본문은 일반적인 함수 클로저 의미론을 사용하여 새로운 스코프에서 실행됩니다. 루프 변수와 전역 참조의 조기 바인딩을 지원하고 클래스 스코프에 정의된 다른 이름에도 접근할 수 있도록, given 절은 헤더 줄에서 명시적 바인딩 연산도 허용합니다.:

# Explicit early binding via given clause
seq = []
for i in range(10):
    seq.append(?.f) given i=i in:
        def f():
            return i
assert [f() for f in seq] == list(range(10))

의미 체계

다음 문장은:

op(?.f, ?.g) given bound_a=a, bound_b=b in:
    def f():
        return bound_a + bound_b
    def g():
        return bound_a - bound_b

다음 코드와 대략적으로 동등합니다(__var는 숨겨진 컴파일러 변수 또는 단순히 인터프리터 스택의 항목을 나타냅니다).:

__arg1 = a
__arg2 = b
def __scope(bound_a, bound_b):
    def f():
        return bound_a + bound_b
    def g():
        return bound_a - bound_b
   return types.SimpleNamespace(**locals())
__ref = __scope(__arg1, __arg2)
__ref.__doc__ = __scope.__doc__
op(__ref.f, __ref.g)

given 절은 본질적으로 생성된 직후 즉시 실행되는 중첩 함수입니다. 명시적으로 전달되지 않은 이름은 일반적인 스코핑 규칙에 따라 조회되므로, 클래스 스코프에 정의된 이름은 표시되지 않습니다. 전방 참조로 선언된 이름은 반환되어 헤더 문에서 사용되며, 주변 네임스페이스에 로컬로 바인딩되지는 않습니다.

문법 변경

현재:

expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) |
             ('=' (yield_expr|testlist_star_expr))*)
del_stmt: 'del' exprlist
pass_stmt: 'pass'
return_stmt: 'return' [testlist]
yield_stmt: yield_expr
raise_stmt: 'raise' [test ['from' test]]
assert_stmt: 'assert' test [',' test]

새 문법:

expr_stmt: testlist_star_expr (augassign (yield_expr|testlist) |
             ('=' (yield_expr|testlist_star_expr))*) [given_clause]
del_stmt: 'del' exprlist [given_clause]
pass_stmt: 'pass' [given_clause]
return_stmt: 'return' [testlist] [given_clause]
yield_stmt: yield_expr [given_clause]
raise_stmt: 'raise' [test ['from' test]] [given_clause]
assert_stmt: 'assert' test [',' test] [given_clause]
given_clause: "given" [(NAME '=' test)+ "in"]":" suite

(문법에서 expr_stmt는 단순 표현식 문뿐만 아니라 대입과 증강 대입도 포함하므로 다소 부정확한 명칭이라는 점에 유의하십시오.)

Note

이러한 제안된 문법 변경은 아직 문장 로컬 네임스페이스에 정의된 이름에 접근하기 위한 전방 참조 표현식 문법을 다루지 않습니다.

새로운 절은 문법의 모호성 생성을 피하기 위해 새로운 종류의 복합 문이 아니라 기존 문들의 선택적 요소로 추가합니다. 다음과 같은 말이 안 되는 구문이 허용되지 않도록 나열된 특정 요소에만 적용합니다.:

break given:
    a = b = 1

import sys given:
    a = b = 1

그러나 위에서 설명한 정확한 문법 변경은 부적절합니다. 여러 단일 행 문을 “\n” 대신 “;”으로 연결할 수 있는 simple_stmt의 정의에 문제를 일으키기 때문입니다.

따라서 위의 문법 변경은 의향을 밝힌 것으로 받아들여야 합니다. 실제 제안은 진지하게 검토되기 전에 simple_stmt 구문 분석 문제를 해결해야 합니다. 이를 위해서는 문법을 상당히 재구성해야 할 가능성이 높습니다. 즉, 임의의 하위 표현식을 포함할 수 있는 문과 그렇지 않은 문을 분리하도록 small_stmt와 flow_stmt를 나눈 다음, simple_stmt 수준에서 그러한 문 중 하나에 given 절을 허용해야 합니다. 다음과 같은 형태입니다.:

stmt: simple_stmt | given_stmt | compound_stmt
simple_stmt: small_stmt (';' (small_stmt | subexpr_stmt))* [';'] NEWLINE
small_stmt: (pass_stmt | flow_stmt | import_stmt |
             global_stmt | nonlocal_stmt)
flow_stmt: break_stmt | continue_stmt
given_stmt: subexpr_stmt (given_clause |
              (';' (small_stmt | subexpr_stmt))* [';']) NEWLINE
subexpr_stmt: expr_stmt | del_stmt | flow_subexpr_stmt | assert_stmt
flow_subexpr_stmt: return_stmt | raise_stmt | yield_stmt
given_clause: "given" (NAME '=' test)* ":" suite

참고로 해당 수준의 현재 정의는 다음과 같습니다.:

stmt: simple_stmt | compound_stmt
simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt |
             import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt

위 변경 사항에 더하여 atom의 정의도 ?를 허용하도록 변경합니다. 이 사용을 관련 given 절이 있는 문으로 제한하는 작업은 컴파일 과정의 이후 단계에서 처리합니다. (초기 구문 분석 단계를 단순화하기 위해 문법이 지나치게 관대한 경우의 다른 제한 사항도 이미 적용하고 있는 AST 생성이 그 단계가 될 가능성이 높습니다.)

새로운 PEP 8 지침

python-ideas ([7], [9])에서 논의한 바와 같이, 일반 변수 대입 대신 언제 given 절을 사용할지에 대한 적절한 지침을 제공하기 위해 새로운 PEP 8지침도 마련해야 합니다.

try 문에 이미 존재하는 유사한 지침을 바탕으로, 이 PEP는 PEP 8의 “프로그래밍 관례” 절에 given 문을 위한 다음과 같은 추가 사항을 제안합니다.

  • 합리적으로 별도의 함수로 분리할 수 있지만 현재 어디에서도 재사용되지 않는 코드에는 given 절을 사용하는 것을 고려하십시오. 이는 어떤 변수가 알고리즘 또는 애플리케이션 상태를 보유하기 위한 것이 아니라 다른 문의 하위 구성 요소를 정의하는 데만 사용되는지를 명확히 나타냅니다. 여러 줄 함수를 호출 가능 객체를 인자로 받는 연산에 전달할 때 특히 유용한 기법입니다.
  • given 절은 간결하게 유지하십시오. 다루기 어려워지면 여러 단계로 나누거나 세부 사항을 별도의 함수로 옮기십시오.

근거

Python의 함수 및 클래스 문은 일반적인 할당문과 비교하여 고유한 특성을 가집니다. 어느 정도는 선언적입니다. 함수 또는 클래스 본문에서 실제 정의의 세부 사항을 계속 진행하기 전에, 정의하려는 이름에 관한 몇 가지 중요한 정보를 코드 독자에게 제시합니다.

선언되는 객체의 이름이 키워드 다음에 처음으로 명시됩니다. 구현 세부 사항에 앞서 제시되는 중요한 정보도 있습니다.

  • 데코레이터(생성되는 객체의 동작에 큰 영향을 줄 수 있으며, 미적인 이유보다는 실용적인 이유로 키워드와 이름보다도 앞에 배치됩니다)
  • 독스트링(헤더 줄 바로 다음의 첫 번째 줄에 위치합니다)
  • 함수 정의의 매개변수, 기본값 및 어노테이션
  • 클래스 정의의 부모 클래스, 메타클래스 및 선택적으로 지정되는 기타 세부 사항(메타클래스에 따라 다릅니다)

이 PEP는 임의의 대입 연산에도 유사한 선언적 스타일을 사용할 수 있도록, 모든 단순 대입문 뒤에 “given” 스위트를 포함할 수 있게 할 것을 제안합니다.:

TARGET = [TARGET2 = ... TARGETN =] EXPR given:
    SUITE

관례상 스위트 본문의 코드는 헤더 줄에서 수행되는 할당 연산을 올바르게 정의하는 데만 집중해야 합니다. 또한 헤더 줄의 연산은 본문을 읽지 않아도 독자가 해당 연산의 목적을 합리적으로 파악할 수 있도록 충분히 설명적이어야 합니다(예를 들어 적절한 변수 이름을 선택해야 합니다).

그러나 이 기능의 최초 동기가 된 사용 사례이기는 하지만, 이 기능을 단순 할당에만 한정하는 것은 지나치게 제한적입니다. 일단 이 기능을 정의한다면, 증강 할당, return 문, yield 표현식, 컴프리헨션 및 애플리케이션 상태를 변경할 수 있는 임의의 표현식에 이 기능을 사용하지 못하게 하는 것은 매우 자의적일 것입니다.

given절은 sorted()와 같은 연산에 일회성 함수를 전달하거나 콜백 기반 이벤트 구동 프로그래밍을 수행할 때 람다 표현식 및 유사한 구문의 일부 용도에 대해 더 읽기 쉬운 대안으로도 기능할 수 있습니다.

모듈 및 클래스 수준의 코드에서 given절은 중간 작업 변수가 결과 네임스페이스를 오염시키지 않도록 del문을 사용하는 방식을 명확하고 안정적으로 대체합니다.

제안된 절을 이해하는 한 가지 유용한 방법은 이를 일반적인 인라인 코드와 연산을 전용 함수로 분리하는 것 사이의 중간 지점으로 보는 것입니다. 이는 인라인 while 루프를 나중에 전용 제너레이터로 분리할 수 있는 것과 같습니다.

설계 논의

키워드 선택

이 제안은 처음에 Haskell의 유사한 구성 요소 이름을 바탕으로 where를 사용했습니다. 그러나 Numpy [4]와 같은 기존 Python 라이브러리에서 이미 SQL 쿼리 조건이라는 의미로 where를 사용하고 있다는 지적이 있었으며, 이로 인해 해당 키워드 선택이 혼동을 일으킬 가능성이 있습니다.

given은 변수 이름으로도 사용할 수 있으므로(새 키워드를 도입할 때 일반적인 __future__ 방식을 사용하여 폐기 예정으로 지정할 수 있지만), 새 절에 대해 의도된 “이 표현식이 사용할 수 있는 추가 변수가 여기에 있습니다”라는 의미와 훨씬 더 강하게 연관됩니다.

with 키워드를 재사용하자는 제안도 있었습니다. 이렇게 하면 새 키워드를 추가하지 않아도 된다는 장점이 있지만, with절과 with문이 비슷하게 보이면서도 완전히 다른 작업을 수행하므로 혼동을 일으킬 가능성도 큽니다. 그렇게 하면 C++와 Perl의 길로 들어서게 됩니다 :)

PEP 403과의 관계

PEP 403(범용 데코레이터 절)은 기존 데코레이터 구문에서 영감을 얻은 덜 급진적인 언어 변경을 통해 이 PEP의 주요 목표를 달성하려고 합니다.

두 PEP는 동일한 작성자가 작성했음에도 서로 직접 경쟁합니다. PEP 403은 현재 상태에서 최소한의 변경만으로 유용한 기능을 달성하려는 미니멀리스트 접근 방식을 나타냅니다. 반면 이 PEP는 더 유연한 독립형 문 설계를 목표로 하며, 이를 위해서는 언어를 더 크게 변경해야 합니다.

제너레이터 표현식의 동작을 올바르게 설명하는 데에는 PEP 403이 더 적합한 반면, 이 PEP는 일반적인 데코레이터 절의 동작을 더 잘 설명할 수 있다는 점에 유의하십시오. 두 PEP 모두 컨테이너 컴프리헨션의 의미론을 충분히 설명할 수 있습니다.

컨테이너 컴프리헨션 및 제너레이터 표현식 설명

제안된 구성 요소의 흥미로운 특징 중 하나는 이를 원시 요소로 사용하여 컨테이너 컴프리헨션의 스코프 지정 및 실행 순서 의미론을 설명할 수 있다는 점입니다.:

seq2 = [x for x in y if q(x) for y in seq if p(y)]

# would be equivalent to

seq2 = ?.result given seq=seq:
    result = []
    for y in seq:
        if p(y):
            for x in y:
                if q(x):
                    result.append(x)

이 확장에서 중요한 점은 컴프리헨션이 클래스 범위에서 잘못 동작하는 것처럼 보이는 이유를 설명한다는 것입니다. 가장 바깥쪽 이터레이터만 클래스 범위에서 평가되고, 모든 조건자, 중첩 이터레이터 및 값 표현식은 중첩된 범위 안에서 평가됩니다.

다만 PEP 403과 달리, 이 PEP의 현재 버전은 제너레이터 표현식에 대해 정확히 동등한 확장을 제공할 수 없습니다. 가장 근접한 방법은 추가적인 스코프 수준을 정의하는 것입니다.:

seq2 = ?.g(seq) given:
    def g(seq):
        for y in seq:
            if p(y):
                for x in y:
                    if q(x):
                        yield x

이 제한은 given 절이 제너레이터 함수가 되도록 허용하면 해결할 수 있으며, 이 경우 ?는 단순한 네임스페이스가 아니라 제너레이터-이터레이터 객체를 가리키게 됩니다.:

seq2 = ? given seq=seq in:
    for y in seq:
        if p(y):
            for x in y:
                if q(x):
                    yield x

그러나 이렇게 하면 “?”의 의미가 상당히 모호해지며, 함수 정의가 실제로 제너레이터인지 여부를 나타내는 독스트링이 일반적으로 포함되는 def 문에서 이미 나타나는 모호함보다도 더 심해집니다.

데코레이터 절 평가 및 적용 설명

데코레이터 절의 평가 및 적용에 대한 표준적인 설명에서는 실행 순서에 따른 단계를 보여 주기 위해 숨겨진 컴파일러 변수라는 개념을 다루어야 합니다. given 문을 사용하면 다음과 같은 데코레이터가 적용된 함수 정의를 작성할 수 있습니다.:

@classmethod
def classname(cls):
    return cls.__name__

대신 대략 다음과 동등한 것으로 설명할 수 있습니다.:

classname = .d1(classname) given:
    d1 = classmethod
    def classname(cls):
        return cls.__name__

예상되는 반론

이를 수행하는 두 가지 방법

이제 값이 사용되는 표현식 앞에서 정의되거나 given 절에서 나중에 정의되는 코드를 많이 작성할 수 있으므로, 두 가지 방법이 생기며 둘 중 하나를 선택할 명확한 방법이 없을 수도 있습니다.

곰곰이 생각해 보면, 이는 “명확하고 유일한 방법”이라는 격언을 잘못 적용한 것이라고 생각합니다. Python은 이미 코드를 작성하는 많은 방법을 제공합니다. for 루프나 while 루프, 함수형 스타일이나 명령형 스타일 또는 객체 지향 스타일을 사용할 수 있습니다. 일반적으로 이 언어는 사람들이 생각하는 방식에 맞는 코드를 작성할 수 있도록 설계되었습니다. 사람마다 생각하는 방식이 다르므로, 코드를 작성하는 방식도 그에 따라 달라집니다.

코드베이스에서 이러한 스타일 관련 문제는 해당 코드를 담당하는 개발 그룹에 맡기는 것이 마땅합니다. 하위 표현식이 한 번만 사용될 예정이더라도, 표현식이 얼마나 복잡해져야 하위 표현식을 분리하여 변수에 할당해야 합니까? 동일한 논리를 구현하는 제너레이터로 인라인 while 루프를 대체해야 하는 시점은 언제입니까? 의견은 서로 다르며, 그것은 괜찮습니다.

그러나 CPython과 표준 라이브러리에는 명시적인 PEP 8 지침이 필요하며, 이는 위의 제안에서 논의되어 있습니다.

순서를 벗어난 실행

given 절의 본문이 절 헤더의 단순문보다 먼저 실행되므로, given 절은 실행 순서를 약간 이상하게 뒤섞습니다. Python의 다른 부분에서 이와 가장 가까운 것은 리스트 컴프리헨션, 제너레이터 표현식 및 조건 표현식에서의 순서를 벗어난 평가와, 데코레이터 함수가 자신이 장식하는 함수에 적용되는 시점의 지연입니다(데코레이터 표현식 자체는 작성된 순서대로 실행됩니다).

이는 사실이지만, 이 문법은 사람들이 스스로 문제에 대해 순서와 다르게 생각하는 경우를 위한 것입니다(적어도 언어의 관점에서는 그렇습니다). 이를 보여 주는 예로 Python 사용자의 다음과 같은 생각을 살펴보십시오.

이 시퀀스의 항목들을 각 항목의 attr1 및 attr2 값에 따라 정렬하고 싶습니다.

lambda 표현식에 익숙하다면 다음과 같이 작성할 수 있습니다.:

sorted_list = sorted(original, key=(lambda v: v.attr1, v.attr2))

이렇게 하면 목적은 달성되지만, Python의 명성에 걸맞은 실행 가능한 의사 코드라는 기준에는 거의 도달하지 못합니다.

특히 lambda를 좋아하지 않는다면, operator 모듈은 키 함수를 인라인으로 정의할 수 있는 대안을 제공합니다.:

sorted_list = sorted(original,
                     key=operator.attrgetter(v. 'attr1', 'attr2'))

다시 말해 목적은 달성하지만, 가장 관대한 독자라도 이를 “실행 가능한 의사 코드”라고 여기지는 않을 것입니다.

앞서 언급한 두 선택지가 모두 추하고 혼란스럽다고 생각하거나, 예외 포착과 같이 표현식으로 표현할 수 없는 로직을 키 함수에 넣어야 한다면, Python은 현재 원래 생각의 순서를 뒤집어 정렬 기준을 먼저 정의하도록 강제합니다.:

def sort_key(item):
    return item.attr1, item.attr2

sorted_list = sorted(original, key=sort_key)

수년 동안 여러 줄 lambda 지원 요청에 대한 상투적인 답변은 “그냥 함수를 정의하십시오”였습니다. 앞서 언급한 선택지들과 마찬가지로 이 방법은 목적을 달성하지만, 사용자가 생각하는 방식과 언어가 표현하도록 허용하는 방식 사이에 실제로 단절을 만듭니다.

저는 이 PEP의 제안이 마침내 Python이 위에서 표현한 종류의 사고에 대해 “실행 가능한 의사 코드”라는 기준에 가까워지도록 할 것이라고 믿습니다.:

sorted_list = sorted(original, key=?.key) given:
    def key(item):
        return item.attr1, item.attr2

모든 것이 사용자의 원래 생각과 동일한 순서로 되어 있으며, 정렬 기준의 이름을 새로 지을 필요조차 없습니다. 키워드 인자 이름을 직접 재사용할 수 있습니다.

이 제안에 대한 가능한 개선안은 “주어진 절의 내용을 키워드 인자로 사용하십시오”라고 말할 수 있는 편리한 축약 구문을 제공하는 것입니다. 전용 구문이 없더라도 다음과 같이 간단히 작성할 수 있습니다: **vars(?).

인트로스펙션에 유해함

모듈과 클래스 내부를 살펴보는 것은 화이트박스 테스트와 대화형 디버깅에 매우 귀중한 도구입니다. given 절은 계산에 사용되는 임시 상태에 대한 접근을 상당히 효과적으로 차단합니다(이 점에서는 현재 del 문을 사용하는 것보다 더 효과적이지는 않습니다).

이는 타당한 우려이지만, 테스트 가능성을 고려한 설계는 프로그래밍의 여러 측면에 걸쳐 있는 문제입니다. 어떤 구성 요소를 독립적으로 테스트해야 한다면, 정보가 테스트 스위트에 노출되도록 given 문을 별도의 문으로 리팩터링해야 합니다. 이는 함수나 제너레이터 내부에 숨겨진 연산을 격리하여 테스트할 수 있도록 단순히 자체 함수로 분리하는 리팩터링과 크게 다르지 않습니다.

실제 세계 영향 평가의 부족

현재 PEP의 예제는 거의 모두 비교적 작은 “장난감” 예제입니다. 이 PEP의 제안은 대규모 코드베이스(예: 표준 라이브러리나 대규모 Twisted 애플리케이션)에 적용하여 실제 코드의 가독성이 진정으로 향상되는 사례를 찾는 시험을 거쳐야 합니다.

하지만 이는 아이디어보다는 PEP의 결함에 가깝습니다. 실제 세계의 문제가 아니라면, 여러 줄 lambda 지원의 부족에 대한 불만이 그렇게 많이 나오지도 않을 것이며 Ruby의 블록 구문도 아마 지금처럼 인기가 높지는 않을 것입니다.

미해결 질문

전방 참조 구문

? 기호는 짧고 현재 사용되지 않으며 “나중에 채워질 무언가가 여기에 빠져 있습니다”라는 의미를 암시하므로, 주어진 네임스페이스에 대한 전방 참조에 사용하도록 제안되었습니다.

이 PEP의 제안은 기존 Python 기능과 깔끔하게 대응하지 않으므로, 이미 사용 중인 기호를 재사용하는 것은 의도적으로 피했습니다.

nonlocalglobal 처리

nonlocalglobalgiven 절 스위트에서 명시적으로 허용되지 않으며, 그 안에 나타나면 구문 오류가 됩니다. 해당 절 안의 def 문 내부에 나타나는 경우에는 정상적으로 작동합니다.

또는 익명 함수가 위의 확장된 형태로 정의된 것처럼 동작하도록 정의할 수도 있습니다.

breakcontinue 처리

breakcontinue는 익명 함수가 위의 확장된 형태로 정의된 것처럼 작동합니다. given 절 스위트에 나타나면 구문 오류가 되지만, 해당 스위트의 일부로서 for 또는 while 루프 안에 나타나는 경우에는 정상적으로 작동합니다.

returnyield 처리

returnyieldgiven 절 스위트에서 명시적으로 허용되지 않으며, 그 안에 나타나면 구문 오류가 됩니다. 해당 절 안의 def 문 내부에 나타나는 경우에는 정상적으로 작동합니다.

예시

이벤트 기반 프로그래밍을 위한 콜백 정의:

# Current Python (definition before use)
def cb(sock):
    # Do something with socket
def eb(exc):
    logging.exception(
        "Failed connecting to %s:%s", host, port)
loop.create_connection((host, port), cb, eb) given:

# Becomes:
loop.create_connection((host, port), ?.cb, ?.eb) given:
    def cb(sock):
        # Do something with socket
    def eb(exc):
        logging.exception(
            "Failed connecting to %s:%s", host, port)

일반적으로 단일 인스턴스만 갖는 “일회성” 클래스 정의:

# Current Python (instantiation after definition)
class public_name():
  ... # However many lines
public_name = public_name(*params)

# Current Python (custom decorator)
def singleton(*args, **kwds):
    def decorator(cls):
        return cls(*args, **kwds)
    return decorator

@singleton(*params)
class public_name():
  ... # However many lines

# Becomes:
public_name = ?.MeaningfulClassName(*params) given:
  class MeaningfulClassName():
    ... # Should trawl the stdlib for an example of doing this

로컬 네임스페이스를 오염시키지 않고 특성 계산하기 (os.py에서):

# Current Python (manual namespace cleanup)
def _createenviron():
  ... # 27 line function

environ = _createenviron()
del _createenviron

# Becomes:
environ = ?._createenviron() given:
    def _createenviron():
      ... # 27 line function

기본 인자 해킹 대체하기 (functools.lru_cache에서):

# Current Python (default argument hack)
def decorating_function(user_function,
               tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
  ... # 60 line function
return decorating_function

# Becomes:
return ?.decorating_function given:
  # Cell variables rather than locals, but should give similar speedup
  tuple, sorted, len, KeyError = tuple, sorted, len, KeyError
  def decorating_function(user_function):
    ... # 60 line function

# This example also nicely makes it clear that there is nothing in the
# function after the nested function definition. Due to additional
# nested functions, that isn't entirely clear in the current code.

가능한 추가 사항

  • 현재 제안에서는 given 절을 단순문에만 추가할 수 있습니다. 복합문에서도 이 아이디어를 사용할 수 있도록 확장하는 것은 (끝에 given 절을 독립적인 스위트로 덧붙임으로써) 충분히 가능하지만, 그렇게 하면 심각한 가독성 문제가 발생합니다. given 절에서 정의된 값이 정의되기 훨씬 전에 사용될 수 있기 때문이며, 이는 데코레이터나 with 문과 같은 다른 기능이 제거하도록 설계된 바로 그런 종류의 가독성 함정입니다.
  • “명시적 조기 바인딩” 변형은 기본 인자 해크를 제거하는 방법에 관한 python-ideas 논의에 적용할 수 있습니다. 함수의 헤더 줄(반환 타입 어노테이션 뒤)에 given 절을 두는 것이 그 질문에 대한 답이 될 수 있습니다.

거부된 대안

  • 이 PEP의 이전 버전에서는 후행 스위트의 이름에 대한 암시적 전방 참조를 허용했으며, 암시적 조기 바인딩 의미론도 사용했습니다. 이 두 아이디어는 표현력을 충분히 향상시키지 못하면서 제안을 상당히 복잡하게 만들었습니다. 명시적 전방 참조와 조기 바인딩을 사용하는 현재 제안은 새로운 구문을 기존 스코프 지정 의미론에 맞추며, 실제로 아이디어를 구현할 가능성을 크게 높입니다.
  • 여기에서 제안한 내용 외에도, 순서가 뒤바뀐 실행을 지원하지 않으면서 이름의 제한된 스코프 지정을 제공하는 두 가지 스위트 “순서대로” 변형도 제안되었습니다. 다중 줄 람다 지원을 요구하는 사람들이 불만을 제기할 때 이 제안들은 대체로 핵심을 놓치고 있다고 생각합니다. 문제는 하위 표현식의 이름을 정하는 것이 특별히 어렵다는 데 있는 것이 아니라, 해당 문장을 사용하는 문장보다 먼저 함수의 이름을 정하면 코드가 개발자가 당면한 문제를 생각하는 방식과 더 이상 일치하지 않는다는 데 있습니다.
  • 이 PEP에서 정의한 구문의 전반적인 구조는 유지하면서 given 절이 암시적으로 생성하는 클로저에 직접 참조할 수 있도록 몇 가지 비공개 시도를 했습니다(예를 들어 ?given 또는 :given과 같은 하위 표현식을 식에서 사용하여 암시된 클로저를 직접 참조함을 나타내고, 이를 통해 로컬 네임스페이스를 생성하기 위해 자동으로 호출되는 것을 방지하는 방식입니다). 이러한 모든 시도는 PEP 403의 더 단순한 데코레이터에서 영감을 받은 제안과 비교할 때 매력적이지 않고 혼란스러운 것으로 나타났습니다.

참조 구현

아직 없습니다. Python 네임스페이스 의미론과 코드 컴파일을 속성으로 익히고 싶다면 부담 없이 시도해 보십시오 ;)

할 일

  • given 절에서 PEP 359와 locals()의 가능한 용도를 언급하십시오.
  • 이를 내부적으로 사용하여 인자가 없는 super() 호출의 구현을 덜 끔찍하게 만들 수 있는지 알아보십시오.

참고 자료