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

Python 개선 제안 한국어 번역

PEP 3141 – 수를 위한 타입 계층

Author:
Jeffrey Yasskin <jyasskin at google.com>
Status:
Final
Type:
Standards Track
Created:
23-Apr-2007
Python-Version:
3.0
Post-History:
25-Apr-2007, 16-May-2007, 02-Aug-2007

Table of Contents

번역·라이선스 안내

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

초록

이 제안은 추상 베이스 클래스(ABC)의 계층을 정의합니다(PEP 3119) 수와 유사한 클래스를 나타내기 위한 것입니다. 다음과 같은 계층을 제안합니다. Number :> Complex :> Real :> Rational :> Integral이며, 여기서 A :> B는 “A가 B의 상위 타입”을 의미합니다. 이 계층은 Scheme의 수치 타워 [2]에서 영감을 받았습니다.

근거

수를 인자로 받는 함수는 해당 수의 속성을 판별할 수 있어야 하며, 타입에 따른 오버로딩이 언어에 추가될 경우 인자의 타입에 따라 오버로드할 수 있어야 합니다. 예를 들어 슬라이싱에서는 인자가 Integrals이어야 하며, math 모듈의 함수에서는 인자가 Real이어야 합니다.

명세

이 PEP는 추상 베이스 클래스의 집합을 명세하고 일부 메서드를 구현하기 위한 일반적인 전략을 제안합니다. 이는 PEP 3119의 용어를 사용하지만, 이 계층 구조는 클래스 집합을 정의하는 모든 체계적인 방법에 의미가 있도록 의도되었습니다.

표준 라이브러리의 타입 검사는 구체적인 내장 타입 대신 이러한 클래스를 사용해야 합니다.

수치 클래스

사람들이 어떤 종류의 수를 예상하는지 모호하게 지정할 수 있도록 Number 클래스로 시작합니다. 이 클래스는 오버로딩에만 도움을 주며, 어떠한 연산도 제공하지 않습니다.

class Number(metaclass=ABCMeta): pass

대부분의 복소수 구현은 해시 가능하겠지만, 이에 의존해야 한다면 명시적으로 확인해야 합니다. 이 계층은 변경 가능한 수도 지원합니다.

class Complex(Number):
    """Complex defines the operations that work on the builtin complex type.

    In short, those are: conversion to complex, bool(), .real, .imag,
    +, -, *, /, **, abs(), .conjugate(), ==, and !=.

    If it is given heterogeneous arguments, and doesn't have special
    knowledge about them, it should fall back to the builtin complex
    type as described below.
    """

    @abstractmethod
    def __complex__(self):
        """Return a builtin complex instance."""

    def __bool__(self):
        """True if self != 0."""
        return self != 0

    @abstractproperty
    def real(self):
        """Retrieve the real component of this number.

        This should subclass Real.
        """
        raise NotImplementedError

    @abstractproperty
    def imag(self):
        """Retrieve the imaginary component of this number.

        This should subclass Real.
        """
        raise NotImplementedError

    @abstractmethod
    def __add__(self, other):
        raise NotImplementedError

    @abstractmethod
    def __radd__(self, other):
        raise NotImplementedError

    @abstractmethod
    def __neg__(self):
        raise NotImplementedError

    def __pos__(self):
        """Coerces self to whatever class defines the method."""
        raise NotImplementedError

    def __sub__(self, other):
        return self + -other

    def __rsub__(self, other):
        return -self + other

    @abstractmethod
    def __mul__(self, other):
        raise NotImplementedError

    @abstractmethod
    def __rmul__(self, other):
        raise NotImplementedError

    @abstractmethod
    def __div__(self, other):
        """a/b; should promote to float or complex when necessary."""
        raise NotImplementedError

    @abstractmethod
    def __rdiv__(self, other):
        raise NotImplementedError

    @abstractmethod
    def __pow__(self, exponent):
        """a**b; should promote to float or complex when necessary."""
        raise NotImplementedError

    @abstractmethod
    def __rpow__(self, base):
        raise NotImplementedError

    @abstractmethod
    def __abs__(self):
        """Returns the Real distance from 0."""
        raise NotImplementedError

    @abstractmethod
    def conjugate(self):
        """(x+y*i).conjugate() returns (x-y*i)."""
        raise NotImplementedError

    @abstractmethod
    def __eq__(self, other):
        raise NotImplementedError

    # __ne__ is inherited from object and negates whatever __eq__ does.

Real ABC는 값이 실수선 위에 있으며 float 내장 타입의 연산을 지원함을 나타냅니다. 실수는 NaN을 제외하면 전순서가 부여됩니다(NaN은 이 PEP에서 사실상 무시합니다).

class Real(Complex):
    """To Complex, Real adds the operations that work on real numbers.

    In short, those are: conversion to float, trunc(), math.floor(),
    math.ceil(), round(), divmod(), //, %, <, <=, >, and >=.

    Real also provides defaults for some of the derived operations.
    """

    # XXX What to do about the __int__ implementation that's
    # currently present on float?  Get rid of it?

    @abstractmethod
    def __float__(self):
        """Any Real can be converted to a native float object."""
        raise NotImplementedError

    @abstractmethod
    def __trunc__(self):
        """Truncates self to an Integral.

        Returns an Integral i such that:
          * i>=0 iff self>0;
          * abs(i) <= abs(self);
          * for any Integral j satisfying the first two conditions,
            abs(i) >= abs(j) [i.e. i has "maximal" abs among those].
        i.e. "truncate towards 0".
        """
        raise NotImplementedError

    @abstractmethod
    def __floor__(self):
        """Finds the greatest Integral <= self."""
        raise NotImplementedError

    @abstractmethod
    def __ceil__(self):
        """Finds the least Integral >= self."""
        raise NotImplementedError

    @abstractmethod
    def __round__(self, ndigits:Integral=None):
        """Rounds self to ndigits decimal places, defaulting to 0.

        If ndigits is omitted or None, returns an Integral,
        otherwise returns a Real, preferably of the same type as
        self. Types may choose which direction to round half. For
        example, float rounds half toward even.

        """
        raise NotImplementedError

    def __divmod__(self, other):
        """The pair (self // other, self % other).

        Sometimes this can be computed faster than the pair of
        operations.
        """
        return (self // other, self % other)

    def __rdivmod__(self, other):
        """The pair (self // other, self % other).

        Sometimes this can be computed faster than the pair of
        operations.
        """
        return (other // self, other % self)

    @abstractmethod
    def __floordiv__(self, other):
        """The floor() of self/other. Integral."""
        raise NotImplementedError

    @abstractmethod
    def __rfloordiv__(self, other):
        """The floor() of other/self."""
        raise NotImplementedError

    @abstractmethod
    def __mod__(self, other):
        """self % other

        See
        https://mail.python.org/pipermail/python-3000/2006-May/001735.html
        and consider using "self/other - trunc(self/other)"
        instead if you're worried about round-off errors.
        """
        raise NotImplementedError

    @abstractmethod
    def __rmod__(self, other):
        """other % self"""
        raise NotImplementedError

    @abstractmethod
    def __lt__(self, other):
        """< on Reals defines a total ordering, except perhaps for NaN."""
        raise NotImplementedError

    @abstractmethod
    def __le__(self, other):
        raise NotImplementedError

    # __gt__ and __ge__ are automatically done by reversing the arguments.
    # (But __le__ is not computed as the opposite of __gt__!)

    # Concrete implementations of Complex abstract methods.
    # Subclasses may override these, but don't have to.

    def __complex__(self):
        return complex(float(self))

    @property
    def real(self):
        return +self

    @property
    def imag(self):
        return 0

    def conjugate(self):
        """Conjugate is a no-op for Reals."""
        return +self

Demo/classes/Rat.py를 정리하여 표준 라이브러리의 rational.py로 승격해야 합니다. 그러면 Rational ABC를 구현하게 됩니다.

class Rational(Real, Exact):
    """.numerator and .denominator should be in lowest terms."""

    @abstractproperty
    def numerator(self):
        raise NotImplementedError

    @abstractproperty
    def denominator(self):
        raise NotImplementedError

    # Concrete implementation of Real's conversion to float.
    # (This invokes Integer.__div__().)

    def __float__(self):
        return self.numerator / self.denominator

그리고 마지막으로 정수입니다.:

class Integral(Rational):
    """Integral adds a conversion to int and the bit-string operations."""

    @abstractmethod
    def __int__(self):
        raise NotImplementedError

    def __index__(self):
        """__index__() exists because float has __int__()."""
        return int(self)

    def __lshift__(self, other):
        return int(self) << int(other)

    def __rlshift__(self, other):
        return int(other) << int(self)

    def __rshift__(self, other):
        return int(self) >> int(other)

    def __rrshift__(self, other):
        return int(other) >> int(self)

    def __and__(self, other):
        return int(self) & int(other)

    def __rand__(self, other):
        return int(other) & int(self)

    def __xor__(self, other):
        return int(self) ^ int(other)

    def __rxor__(self, other):
        return int(other) ^ int(self)

    def __or__(self, other):
        return int(self) | int(other)

    def __ror__(self, other):
        return int(other) | int(self)

    def __invert__(self):
        return ~int(self)

    # Concrete implementations of Rational and Real abstract methods.
    def __float__(self):
        """float(self) == float(int(self))"""
        return float(int(self))

    @property
    def numerator(self):
        """Integers are their own numerators."""
        return +self

    @property
    def denominator(self):
        """Integers have a denominator of 1."""
        return 1

연산 및 __magic__ 메서드의 변경

float에서 int로, 더 일반적으로는 Real에서 Integral로 더 정확하게 좁히는 것을 지원하기 위해, 해당 라이브러리 함수에서 호출할 다음과 같은 새로운 __magic__ 메서드를 제안합니다. 이 메서드들은 모두 Reals가 아니라 Integrals를 반환합니다.

  1. __trunc__(self)는 새로운 내장 함수 trunc(x)에서 호출되며, 0과 x 사이에서 x에 가장 가까운 Integral을 반환합니다.
  2. __floor__(self)math.floor(x)에서 호출되며, <= x인 가장 큰 Integral을 반환합니다.
  3. __ceil__(self)math.ceil(x)에서 호출되며, >= x인 가장 작은 Integral을 반환합니다.
  4. __round__(self)round(x)에서 호출되며, 타입이 선택하는 방식으로 절반을 반올림하여 x에 가장 가까운 Integral을 반환합니다. float은 3.0에서 절반을 짝수 쪽으로 반올림하도록 변경됩니다. 2개 인자를 받는 버전인 __round__(self, ndigits)도 있으며, round(x, ndigits)에서 호출되고 Real을 반환해야 합니다.

2.6에서는 math.floor, math.ceilround가 계속해서 부동 소수를 반환합니다.

float이 구현하는 int() 변환은 trunc()와 동등합니다. 일반적으로 int() 변환은 먼저 __int__()를 시도하고, 찾을 수 없으면 __trunc__()를 시도해야 합니다.

complex.__{divmod,mod,floordiv,int,float}__도 사라집니다. 혼란스러워하는 포터를 돕기 위해 알맞은 오류 메시지를 제공하면 좋겠지만, help(complex)에 나타나지 않는 것이 더 중요합니다.

타입 구현자 참고 사항

구현자는 같은 수가 같게 취급되고 동일한 값으로 해시되도록 주의해야 합니다. 실수의 서로 다른 확장이 두 가지 있는 경우에는 이것이 미묘한 문제가 될 수 있습니다. 예를 들어 복소수 타입은 다음과 같이 hash()를 합리적으로 구현할 수 있습니다.:

def __hash__(self):
    return hash(complex(self))

그러나 내장 complex의 범위나 정밀도를 벗어나는 값에 대해서는 주의해야 합니다.

숫자 ABC 추가하기

물론 숫자에 대해 가능한 ABC는 더 많으며, 그러한 ABC를 추가할 가능성을 배제한다면 이는 좋지 않은 계층 구조가 됩니다. 다음과 같이 ComplexReal 사이에 MyFoo를 추가할 수 있습니다.:

class MyFoo(Complex): ...
MyFoo.register(Real)

산술 연산 구현하기

혼합 모드 연산이 두 인자의 타입을 모두 알고 있는 구현을 호출하거나, 두 인자를 가장 가까운 내장 타입으로 변환한 다음 그곳에서 연산을 수행하도록 산술 연산을 구현하려고 합니다. Integral의 서브타입에서는 이는 __add__와 __radd__를 다음과 같이 정의해야 한다는 의미입니다.:

class MyIntegral(Integral):

    def __add__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(self, other)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(self, other)
        else:
            return NotImplemented

    def __radd__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(other, self)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(other, self)
        elif isinstance(other, Integral):
            return int(other) + int(self)
        elif isinstance(other, Real):
            return float(other) + float(self)
        elif isinstance(other, Complex):
            return complex(other) + complex(self)
        else:
            return NotImplemented

Complex의 서브클래스에서 혼합 타입 연산이 이루어지는 경우에는 5가지 서로 다른 사례가 있습니다. MyIntegral과 OtherTypeIKnowAbout을 참조하지 않는 위의 모든 코드를 “상용구”라고 부르겠습니다. aComplex의 서브타입인 A의 인스턴스이며 (a : A <: Complex), b : B <: Complex입니다. a + b를 살펴보겠습니다.

  1. A가 b를 받아들이는 __add__를 정의하면 아무 문제가 없습니다.
  2. A가 상용구 코드로 대체하고 __add__에서 값을 반환한다면, B가 더 지능적인 __radd__를 정의할 가능성을 놓치게 되므로 상용구는 __add__에서 NotImplemented를 반환해야 합니다. (또는 A가 __add__를 전혀 구현하지 않을 수도 있습니다.)
  3. 그러면 B의 __radd__가 시도될 기회를 얻습니다. B가 a를 받아들이면 아무 문제가 없습니다.
  4. 상용구로 대체하는 경우에는 시도할 수 있는 메서드가 더 이상 없으므로, 이 지점에 기본 구현이 있어야 합니다.
  5. B <: A인 경우 Python은 A.__add__보다 먼저 B.__radd__를 시도합니다. 이는 A에 대한 지식을 바탕으로 구현되었으므로, Complex에 위임하기 전에 그러한 인스턴스를 처리할 수 있기 때문에 괜찮습니다.

A<:ComplexB<:Real이 다른 지식을 공유하지 않는다면, 적절한 공통 연산은 내장 complex가 관여하는 연산이며 두 __radd__ 모두 그곳에 도달하므로 a+b == b+a가 됩니다.

거부된 대안

이 PEP의 초기 버전은 Haskell Numeric Prelude [1]에서 영감을 받은 대수적 계층 구조를 정의했으며, 여기에는 MonoidUnderPlus, AdditiveGroup, Ring 및 Field가 포함되었습니다. 또한 숫자에 이르기 전에 가능한 여러 다른 대수적 타입도 언급했습니다. 벡터와 행렬을 사용하는 사람들에게 이것이 유용할 것으로 예상했지만, NumPy 커뮤니티는 실제로 관심을 보이지 않았고, xX <: MonoidUnderPlus의 인스턴스이고 yY <: MonoidUnderPlus의 인스턴스이더라도 x + y가 여전히 의미가 없을 수 있다는 문제에 부딪혔습니다.

그런 다음 Gaussian Integers와 Z/nZ 같은 것을 포함하도록 숫자에 훨씬 더 분기된 구조를 부여했습니다. 이러한 타입은 Complex일 수 있지만 나눗셈과 같은 연산을 반드시 지원하지는 않습니다. 커뮤니티는 이것이 Python에 너무 복잡하다고 판단했으므로, 이제 제안의 범위를 줄여 Scheme의 수치 타워와 훨씬 더 유사하게 만들었습니다.

Decimal 타입

저자들과 협의한 결과, 현재로서는 Decimal 형식을 수치 탑의 일부로 만들지 않기로 결정했습니다.

참고 문헌

감사의 말

이 PEP를 처음부터 작성하도록 격려해 준 Neal Norwitz, numpy 사람들이 대수적 개념에 실제로 관심이 없다는 점을 지적해 준 Travis Oliphant, Scheme이 이미 이를 수행했다는 점을 상기시켜 준 Alan Isaac, 그리고 개념을 다듬는 데 도움을 준 Guido van Rossum과 메일링 리스트의 많은 분께 감사드립니다.