numbers — 숫자 추상 베이스 클래스

numbers — 숫자 추상 베이스 클래스

numbers 모듈(PEP 3141)은 점진적으로 더 많은 연산을 정의하는 숫자 추상 베이스 클래스(ABC) 계층을 정의해요. 이 모듈에서 정의된 어떤 타입도 인스턴스화하도록 의도되지 않았어요.

출처: Python 표준 라이브러리

본문

class numbers.Number

숫자 계층의 뿌리예요. 인자 x가 숫자인지만 확인하고 싶고 종류는 상관없다면 isinstance(x, Number)를 쓰면 돼요.

숫자 타워 (The numeric tower)

class numbers.Complex

이 타입의 하위 클래스는 복소수를 나타내며 내장 complex 타입에서 동작하는 연산을 포함해요. 그것들은: complexbool로의 변환, real, imag, +, -, *, /, **, abs(), conjugate(), ==, !=예요. -!=를 제외한 모두가 추상이에요.

  • real — 추상. 이 숫자의 실수 성분을 가져와요.
  • imag — 추상. 이 숫자의 허수 성분을 가져와요.
  • abstractmethod conjugate() — 추상. 복소 켤레를 돌려줘요. 예: (1+3j).conjugate() == (1-3j).

class numbers.Real

Complex에 대해, Real은 실수에서 동작하는 연산을 추가해요. 간단히 말해: float로의 변환, math.trunc(), round(), math.floor(), math.ceil(), divmod(), //, %, <, <=, >, >=예요. Real은 또한 complex(), real, imag, conjugate()에 대한 기본값도 제공해요.

class numbers.Rational

Real의 하위 타입이고 numeratordenominator 속성을 추가해요. float()에 대한 기본값도 제공해요. numeratordenominator 값은 Integral의 인스턴스이고, 분모가 양수인 기약분수(lowest terms)여야 해요.

  • numerator — 추상. 이 유리수의 분자.
  • denominator — 추상. 이 유리수의 분모.

class numbers.Integral

Rational의 하위 타입이고 int로의 변환을 추가해요. float(), numerator, denominator에 대한 기본값을 제공해요. 나머지를 포함한 pow()와 비트 문자열 연산 <<, >>, &, ^, |, ~에 대한 추상 메서드를 추가해요.

타입 구현자를 위한 참고

구현자는 같은 수를 같게 만들고 같은 값으로 해시해야 한다는 점에 주의해야 해요. 실수의 두 가지 다른 확장이 있다면 이는 미묘할 수 있어요. "숫자 타입의 해싱(Hashing of numeric types)"도 참고하세요.

더 많은 숫자 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가지 다른 경우가 있어요. 위 코드에서 MyIntegralOtherTypeIKnowAbout을 언급하지 않는 부분을 모두 "boilerplate"라고 부를게요. aComplex의 하위 타입인 A의 인스턴스(a : A <: Complex)이고, b : B <: Complex예요. a + b를 생각해 보죠:

  1. Ab를 받아들이는 __add__()를 정의하면, 모든 게 잘 돼요.
  2. A가 boilerplate 코드로 빠지고 __add__()에서 값을 돌려주면, B가 더 똑똑한 __radd__()를 정의했을 가능성을 놓치게 돼요. 그래서 boilerplate는 __add__()에서 NotImplemented를 돌려줘야 해요. (또는 A__add__()를 아예 구현하지 않을 수도 있어요.)
  3. 그러면 B__radd__()가 기회를 얻어요. a를 받아들이면 모든 게 잘 돼요.
  4. 그것도 boilerplate로 빠지면, 시도할 수 있는 메서드가 더 없으므로 여기가 기본 구현이 있어야 할 자리예요.
  5. B <: A이면, Python은 A.__add__보다 B.__radd__를 먼저 시도해요. A에 대한 지식으로 구현됐으니, Complex에 위임하기 전에 그 인스턴스를 처리할 수 있으므로 괜찮아요.
  6. A <: ComplexB <: Real이 다른 지식을 공유하지 않는다면, 적절한 공유 연산은 내장 complex를 포함하는 것이고, 두 __radd__()가 거기로 모여서 a+b == b+a가 돼요.

주어진 타입의 대부분 연산은 매우 비슷하므로, 주어진 연산자의 순방향·역방향 인스턴스를 생성하는 헬퍼 함수를 정의하는 게 유용할 수 있어요. 예를 들어 fractions.Fraction은 이렇게 사용해요:

def _operator_fallbacks(monomorphic_operator, fallback_operator):
    def forward(a, b):
        if isinstance(b, (int, Fraction)):
            return monomorphic_operator(a, b)
        elif isinstance(b, float):
            return fallback_operator(float(a), b)
        elif isinstance(b, complex):
            return fallback_operator(complex(a), b)
        else:
            return NotImplemented
    forward.__name__ = '__' + fallback_operator.__name__ + '__'
    forward.__doc__ = monomorphic_operator.__doc__

    def reverse(b, a):
        if isinstance(a, Rational):
            # Includes ints.
            return monomorphic_operator(a, b)
        elif isinstance(a, Real):
            return fallback_operator(float(a), float(b))
        elif isinstance(a, Complex):
            return fallback_operator(complex(a), complex(b))
        else:
            return NotImplemented
    reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
    reverse.__doc__ = monomorphic_operator.__doc__

    return forward, reverse

def _add(a, b):
    """a + b"""
    return Fraction(a.numerator * b.denominator +
                    b.numerator * a.denominator,
                    a.denominator * b.denominator)

__add__, __radd__ = _operator_fallbacks(_add, operator.add)

# ...

더 알아보기

  • math — 수학 함수.
  • fractions, decimal — numbers ABC를 활용하는 구체적 숫자 타입.