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

Python 개선 제안 한국어 번역

PEP 591 – typing에 final 한정자 추가

Author:
Michael J. Sullivan <sully at msully.net>, Ivan Levkivskyi <levkivskyi at gmail.com>
BDFL-Delegate:
Guido van Rossum <guido at python.org>
Discussions-To:
Typing-SIG list
Status:
Final
Type:
Standards Track
Topic:
Typing
Created:
15-Mar-2019
Python-Version:
3.8
Post-History:

Resolution:
Typing-SIG message

Table of Contents

번역·라이선스 안내

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

Important

This PEP is a historical document: see @final/@typing.finalFinal/typing.Final for up-to-date specs and documentation. Canonical typing specs are maintained at the typing specs site; runtime typing behaviour is described in the CPython documentation.

×

See the typing specification update process for how to propose changes to the typing spec.

초록

이 PEP는 다음 세 가지 관련 목적을 위해 typing 모듈에 “final” 한정자를 추가할 것을 제안합니다. 이는 final 데코레이터와 Final 타입 어노테이션의 형태로 제공됩니다.

  • 메서드를 오버라이드해서는 안 된다고 선언합니다.
  • 클래스를 서브클래싱해서는 안 된다고 선언합니다.
  • 변수나 속성을 재할당해서는 안 된다고 선언합니다.

동기

final 데코레이터

현재 typing 모듈에는 타입 검사기 수준에서 상속이나 오버라이딩의 사용을 제한할 방법이 없습니다. 이는 다른 객체 지향 언어(예: Java)에서 일반적인 기능이며, 클래스의 가능한 동작 범위를 줄이고 추론을 쉽게 하는 데 유용합니다.

final 클래스나 메서드가 유용할 수 있는 상황은 다음과 같습니다.

  • 클래스가 서브클래싱되도록 설계되지 않았거나 메서드가 오버라이드되도록 설계되지 않은 경우입니다. 예상대로 작동하지 않거나 오류가 발생하기 쉬울 수 있습니다.
  • 서브클래싱이나 오버라이딩으로 인해 코드를 이해하거나 유지 관리하기가 더 어려워지는 경우입니다. 예를 들어, 베이스 클래스와 서브클래스 간에 불필요하게 긴밀한 결합이 발생하는 것을 방지하려는 경우입니다.
  • 향후 클래스 구현을 임의로 변경할 자유를 유지하려는 경우이며, 이러한 변경으로 서브클래스가 손상될 수 있습니다.

Final 어노테이션

현재 typing 모듈에는 변수에 값을 할당하지 않을 것임을 나타낼 방법이 없습니다. 이는 다음과 같은 여러 상황에서 유용한 기능입니다.

  • 모듈 및 클래스 수준 상수의 의도하지 않은 수정을 방지하고, 검사 가능한 방식으로 이를 상수로 문서화합니다.
  • 서브클래스가 오버라이드할 수 없는 읽기 전용 속성을 생성합니다. (@property를 사용하면 속성을 읽기 전용으로 만들 수 있지만 오버라이딩은 방지하지 못합니다.)
  • 일반적으로 리터럴이 필요한 상황에서 이름을 사용할 수 있도록 합니다(예를 들어 NamedTuple의 필드 이름, isinstance에 전달되는 타입 튜플 또는 Literal 타입의 인자를 받는 함수의 인자(PEP 586)로 사용할 수 있습니다).

사양

final 데코레이터

typing.final 데코레이터는 상속 및 오버라이딩의 사용을 제한하는 데 사용합니다.

타입 검사기는 @final로 데코레이션된 클래스가 서브클래싱되지 않도록 하고, @final로 데코레이션된 메서드가 서브클래스에서 오버라이드되지 않도록 금지해야 합니다. 메서드 데코레이터 버전은 모든 인스턴스 메서드, 클래스 메서드, 정적 메서드 및 속성에 사용할 수 있습니다.

예를 들어 다음과 같습니다.:

from typing import final

@final
class Base:
    ...

class Derived(Base):  # Error: Cannot inherit from final class "Base"
    ...

그리고 다음과 같습니다.:

from typing import final

class Base:
    @final
    def foo(self) -> None:
        ...

class Derived(Base):
    def foo(self) -> None:  # Error: Cannot override final attribute "foo"
                            # (previously declared in base class "Base")
        ...

오버로드된 메서드의 경우 @final을 구현부에 배치해야 합니다(또는 스텁에서는 첫 번째 오버로드에 배치해야 합니다).:

from typing import Any, overload

class Base:
    @overload
    def method(self) -> None: ...
    @overload
    def method(self, arg: int) -> int: ...
    @final
    def method(self, x=None):
        ...

메서드가 아닌 함수에 @final을 사용하는 것은 오류입니다.

Final 어노테이션

typing.Final 타입 한정자는 변수나 특성이 재할당, 재정의 또는 오버라이드되지 않아야 함을 나타내는 데 사용합니다.

구문

Final은 여러 형식 중 하나로 사용할 수 있습니다.

  • 명시적 타입과 함께 Final[<type>] 구문을 사용합니다. 예제입니다.:
    ID: Final[float] = 1
    
  • 타입 어노테이션 없이 사용합니다. 예제입니다.:
    ID: Final = 1
    

    타입 검사기는 일반적인 타입 추론 메커니즘을 적용하여 ID의 타입을 결정해야 합니다(여기서는 int일 가능성이 높습니다). 제네릭 클래스의 경우와 달리 이것은 Final[Any]같지 않다는 점에 유의하십시오.

  • 클래스 본문과 스텁 파일에서는 오른쪽 항을 생략하고 ID: Final[float]이라고만 작성할 수 있습니다. 오른쪽 항을 생략하는 경우 Final에 명시적인 타입 인자가 있어야 합니다.
  • 마지막으로 self.id: Final = 1과 같이 사용할 수 있습니다(대괄호 안에 타입을 선택적으로 지정할 수도 있습니다). 이는 __init__ 메서드에서 오직 허용되므로, 인스턴스가 생성될 때 final 인스턴스 속성이 한 번만 할당됩니다.

의미와 예제

final 이름을 정의하는 두 가지 주요 규칙은 다음과 같습니다.

  • 주어진 속성에 대해 모듈 또는 클래스마다 최대 하나의 final 선언만 존재할 수 있습니다. 이름이 같은 클래스 수준 상수와 인스턴스 수준 상수를 별도로 정의할 수는 없습니다.
  • final 이름에는 정확히 하나의 할당만 있어야 합니다.

따라서 타입 검사기는 타입 검사를 받는 코드에서 final 이름에 대한 추가 할당을 방지해야 합니다.:

from typing import Final

RATE: Final = 3000

class Base:
    DEFAULT_ID: Final = 0

RATE = 300  # Error: can't assign to final attribute
Base.DEFAULT_ID = 1  # Error: can't override a final attribute

런타임에서는 이후 반복에서 동일한 변수에 여러 번 할당하는 것을 확인하게 되므로, 타입 검사기가 루프 내부의 Final 선언을 허용하지 않아도 됩니다.

또한 타입 검사기는 서브클래스에서 final 특성이 오버라이드되지 않도록 방지해야 합니다.:

from typing import Final

class Window:
    BORDER_WIDTH: Final = 2.5
    ...

class ListView(Window):
    BORDER_WIDTH = 3  # Error: can't override a final attribute

초기화 표현식 없이 클래스 본문에 선언된 final 특성은 __init__ 메서드에서 초기화해야 합니다(스텁 파일은 제외합니다).:

class ImmutablePoint:
    x: Final[int]
    y: Final[int]  # Error: final attribute without an initializer

    def __init__(self) -> None:
        self.x = 1  # Good

타입 검사기는 클래스 본문에서 초기화된 final 특성을 클래스 변수로 추론해야 합니다. 변수에 ClassVarFinal을 함께 어노테이션해서는 안 됩니다.

Final은 할당 또는 변수 어노테이션에서 가장 바깥쪽 타입으로만 사용할 수 있습니다. 그 밖의 위치에서 사용하면 오류입니다. 특히 함수 인자의 어노테이션에는 Final을 사용할 수 없습니다.:

x: List[Final[int]] = []  # Error!

def fun(x: Final[List[int]]) ->  None:  # Error!
    ...

이름을 final로 선언하면 해당 이름이 다른 값에 다시 바인딩되지 않는 것만 보장하며, 값 자체를 변경할 수 없게 만들지는 않는다는 점에 유의하십시오. 변경할 수 없는 ABC와 컨테이너를 Final과 함께 사용하면 이러한 값의 변경을 방지할 수 있습니다.:

x: Final = ['a', 'b']
x.append('c')  # OK

y: Final[Sequence[str]] = ['a', 'b']
y.append('x')  # Error: "Sequence[str]" has no attribute "append"
z: Final = ('a', 'b')  # Also works

타입 검사기는 리터럴로 초기화된 final 이름의 사용을 해당 리터럴로 대체한 것처럼 처리해야 합니다. 예를 들어 다음과 같은 사용이 허용되어야 합니다.:

from typing import NamedTuple, Final

X: Final = "x"
Y: Final = "y"
N = NamedTuple("N", [(X, int), (Y, int)])

참조 구현

mypy [1] 타입 검사기는 Finalfinal을 지원합니다. 런타임 구성 요소의 참조 구현은 typing_extensions [2] 모듈에서 제공됩니다.

거부되었거나 보류된 아이디어

Const라는 이름도 Final 타입 어노테이션의 이름으로 고려되었습니다. 두 개념이 서로 관련되어 있어 일관성을 유지하는 것이 가장 좋아 보였기 때문에, 대신 Final이라는 이름이 선택되었습니다.

final을 추가로 도입하는 대신 단일 이름 Final만 사용하는 것도 고려했지만, @Final은 저희에게 너무 어색해 보였습니다.

final 클래스와 관련된 기능으로는 Scala 스타일의 sealed 클래스가 있을 수 있는데, 이는 클래스가 같은 모듈에 정의된 클래스에 의해서만 상속될 수 있도록 허용하는 방식입니다. sealed 클래스는 패턴 매칭과 결합할 때 가장 유용해 보이므로, 저희의 경우에는 그 복잡성을 정당화할 만큼 필요해 보이지 않습니다. 이는 추후에 다시 검토될 수 있습니다.

클래스에 @final 데코레이터를 붙여 런타임에 서브클래싱을 동적으로 방지하게 하는 것도 가능할 것입니다. 하지만 typing에서는 다른 어떤 것도 런타임 강제를 수행하지 않으므로, final 역시 마찬가지입니다. 런타임 강제와 정적 검사가 모두 필요한 경우를 위한 해결책은 다음과 같은 방식(아마도 지원 모듈에서)을 사용하는 것입니다.:

if typing.TYPE_CHECKING:
    from typing import final
else:
    from runtime_final import final

참고 자료