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 객체에 대한 참조가 됩니다.

주된 동기는 좀 더 선언적인 프로그래밍 스타일을 가능하게 하는 것입니다. 이 스타일에서는 수행할 연산을 독자에게 먼저 제시하고, 필요한 하위 계산의 세부 사항은 뒤따르는 들여쓰기된 스위트에서 제시합니다. 대표적인 예로, 이는 일반 대입문을 class 및 def 문과 동등한 위치로 끌어올립니다. 이들 문에서는 정의할 항목의 이름이 그 항목의 값을 계산하는 방법에 대한 세부 사항보다 먼저 독자에게 제시됩니다. 또한 이름 있는 함수를 “여러 줄 람다” 방식으로 사용할 수 있게 해 줍니다. 이 방식에서는 이름이 현재 표현식에서 자리 표시자로만 사용되고, 실제 정의는 뒤따르는 스위트에서 이루어집니다.

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

given 절과, 지정된 연산을 수행하는 별도의 함수 정의 사이의 관계가 명시적인 while 루프와, 그 while 루프와 동일한 연산 순서를 만들어 내는 제너레이터 사이의 기존 관계와 비슷해지도록 하는 것이 의도입니다.

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

제안

이 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

새 기호 ?는 given 네임스페이스를 참조하는 데 사용됩니다. 이것은 types.SimpleNamespace 인스턴스이므로, ?.sort_key는 given 절에 정의된 함수 이름에 대한 전방 참조 역할을 합니다.

given 절에는 독스트링을 둘 수 있으며, 이 독스트링은 결과 네임스페이스의 __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

그러나 위에서 설명한 정확한 문법 변경은 simple_stmt(여러 개의 한 줄 문을 “\n” 대신 “;”로 연결할 수 있게 해 줍니다)의 정의에 문제를 일으키므로 적절하지 않습니다.

따라서 위의 문법 변경은 의도를 밝힌 것으로 받아들여야 합니다. 실제 제안이 진지하게 검토되려면 먼저 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의 “Programming Conventions” 섹션에 given 문에 관한 다음 내용을 추가할 것을 제안합니다:

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

근거

Python의 함수 문과 클래스 문은 일반적인 대입문과 비교해 독특한 성질을 지닙니다. 어느 정도 선언적이라는 점입니다. 이 문들은 함수나 클래스 본문에서 실제 정의를 자세히 다루기 전에, 이제 정의될 이름에 관한 핵심 정보를 코드를 읽는 사람에게 먼저 제시합니다.

선언되는 객체의 이름은 키워드 바로 다음에 가장 먼저 나옵니다. 그 밖의 중요한 정보도 구현 세부 사항보다 앞에 놓이는 대우를 받습니다:

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

이 PEP는 모든 단순 대입문 뒤에 “given” 스위트를 둘 수 있도록 허용하여, 임의의 대입 연산에서도 이와 비슷한 선언적 스타일을 사용할 수 있게 하자고 제안합니다:

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

관례상 스위트 본문의 코드는 오로지 헤더 줄에서 수행되는 대입 연산을 올바르게 정의하는 데에만 집중해야 합니다. 또한 헤더 줄의 연산은 (예를 들어 적절한 변수 이름을 골라) 충분히 설명적이어야 하며, 그래야 읽는 사람이 스위트 본문을 읽지 않고도 연산의 목적을 어느 정도 파악할 수 있습니다.

그러나 단순 대입이 이 기능을 처음 구상하게 된 사용 사례이기는 하지만, 이 기능을 단순 대입에만 한정하는 것은 지나치게 제한적입니다. 일단 이 기능이 정의되고 나면 복합 대입, return 문, yield 표현식, 컴프리헨션, 그리고 애플리케이션 상태를 변경할 수 있는 임의의 표현식에 이 기능을 쓰지 못하게 막는 것은 상당히 자의적인 일이 됩니다.

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

모듈 및 클래스 수준의 코드에서 given 절은, 임시 작업 변수가 결과 네임스페이스를 오염시키지 않도록 del 문을 사용하던 방식을 명확하고 신뢰성 있게 대체하는 역할을 할 것입니다.

제안된 절을 이해하는 데 유용할 수 있는 한 가지 방법은, 이 절을 기존의 인라인 코드와 연산을 전용 함수로 분리하는 방식 사이의 중간 지점으로 생각하는 것입니다. 이는 인라인 while 루프를 결국 전용 제너레이터로 분리해 낼 수 있는 것과 마찬가지입니다.

설계 논의

키워드 선택

이 제안은 처음에 Haskell의 유사한 구문 이름을 따서 where를 사용했습니다. 그러나 기존 Python 라이브러리 중에는 (Numpy [4] 등) 이미 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 문의 의미가 모호한 것보다도 더 심해집니다(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 값에 따라 정렬하고 싶습니다.

Python의 lambda 표현식에 익숙한 사람이라면 다음과 같이 작성할 수도 있습니다:

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

이렇게 해도 원하는 결과는 얻지만, Python의 명성에 걸맞은 executable pseudocode라는 기준에는 거의 미치지 못합니다.

특별히 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)

여러 줄 람다를 지원해 달라는 요청에 수년 동안 판에 박힌 듯 돌아온 답은 “그냥 함수를 정의하십시오”였습니다. 위의 방식들과 마찬가지로 이 방법으로도 목적은 달성되지만, 사용자가 생각하는 것과 언어가 표현하도록 허용하는 것 사이에 실제로 단절이 있음을 보여 줍니다.

저는 이 PEP의 제안이 위와 같은 종류의 생각에 대해 마침내 Python이 “실행 가능한 의사 코드”라는 기준에 가까워지도록 해 줄 것이라고 믿습니다:

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

모든 것이 사용자가 원래 생각한 순서 그대로이며, 정렬 기준에 이름을 붙일 필요조차 없습니다. 키워드 인자 이름을 그대로 재사용할 수 있기 때문입니다.

이러한 제안을 개선하는 방안으로 “given 절의 내용을 키워드 인자로 사용하라”는 뜻을 나타내는 편리한 축약 문법을 제공할 수 있습니다. 전용 문법이 없더라도 이는 간단히 **vars(?)로 작성할 수 있습니다.

인트로스펙션에 해로움

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

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

실제 영향 평가의 부재

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

하지만 이는 아이디어 자체보다는 PEP의 결함에 가깝습니다. 이것이 실제 문제가 아니었다면 여러 줄 람다 지원이 없다는 불만이 이렇게 많이 나오지 않았을 것이고, Ruby의 블록 구문도 아마 이만큼 인기를 끌지는 못했을 것입니다.

미해결 질문

전방 참조를 위한 문법

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

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

nonlocal과 global의 처리

nonlocal과 global은 given 절 스위트에서 명시적으로 금지되며, 사용하면 문법 오류가 됩니다. 해당 스위트 안의 def 문 내부에 나타나는 경우에는 정상적으로 동작합니다.

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

break와 continue의 처리

break와 continue는 익명 함수가 위의 확장에서처럼 정의된 것처럼 동작합니다. 이들이 given 절 스위트에 나타나면 문법 오류가 되지만, 해당 스위트에 포함된 for나 while 루프 안에 있으면 정상적으로 동작합니다.

return과 yield의 처리

return과 yield는 given 절 스위트에서 명시적으로 금지되며, 사용하면 문법 오류가 됩니다. 해당 스위트 안의 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 네임스페이스 의미와 코드 컴파일을 속성으로 익히고 싶다면 얼마든지 시도해 보십시오 ;)

할 일

  • PEP 359와 given 절에서 locals()를 활용할 수 있는 방법 언급하기
  • 이를 내부적으로 사용하여 인자 없는 super() 호출의 구현을 덜 끔찍하게 만들 수 있는지 알아보기

참고 문헌