numbers --- 数の抽象基底クラス

ソースコード: Lib/numbers.py


numbers モジュール (PEP 3141) は、下位になるほど多くの演算を定義する、数値の 抽象基底クラス の階層を定義しています。 このモジュールで定義する型は、どれもインスタンス化を目的としたものではありません。

class numbers.Number

数の階層の根。引数 x が、種類は何であれ、数であるということだけチェックしたい場合、isinstance(x, Number) が使えます。

数値塔

class numbers.Complex

この型のサブクラスは複素数を表し、組み込みの complex 型を受け付ける演算を含みます。それらは: complex および bool への変換、 real, imag, +, -, *, /, **, abs(), conjugate(), ==, != です。 - と != 以外の全てのものは抽象メソッドや抽象プロパティです。

real

抽象プロパティ。この数の実部を取り出します。

imag

抽象プロパティ。この数の虚部を取り出します。

abstractmethod conjugate()

抽象メソッド。 複素共役を返します。 たとえば、(1+3j).conjugate() == (1-3j) です。

class numbers.Real

Real は Complex に実数の演算を追加します。

簡潔に言うとそれらは: float への変換, math.trunc(), round(), math.floor(), math.ceil(), divmod(), //, %, <, <=, > および >= です。

Real はまた complex(), real, imag および conjugate() のデフォルトを提供します。

class numbers.Rational

Real をサブタイプ化し numerator と denominator のプロパティを加えたものです。これは float() のデフォルトも提供します。

numerator と denominator の値は Integral のインスタンスでなければならず、 denominator が正の既約分数になっていなければなりません。

numerator

Abstract. The numerator of this rational number.

denominator

Abstract. The denominator of this rational number.

class numbers.Integral

Rational をサブタイプ化し int への変換が加わります。 float(), numerator, denominator のデフォルトを提供します。法 (訳注: 割る数、除数のこと) を持つ pow() に対する抽象メソッドと、ビット列演算 <<, >>, &, ^, |, ~ を追加します。

型を実装する人向けの注意事項

Implementers should be careful to make equal numbers equal and hash them to the same values. This may be subtle if there are two different extensions of the real numbers. See also Hashing of numeric types.

さらに数のABCを追加する

数に対する ABC が他にも多く存在しうることは、言うまでもありません。それらの ABC を階層に追加する可能性が閉ざされるとしたら、その階層は貧相な階層でしかありません。たとえば、 MyFoo を Complex と Real の間に付け加えるには、次のようにします:

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

ここには5つの異なる Complex のサブクラス間の混在型の演算があります。上のコードの中で MyIntegral と OtherTypeIKnowAbout に触れない部分を "ボイラープレート" と呼ぶことにしましょう。 a を Complex のサブタイプである 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__() に機会が回ってきます。 これが a を受け付ければ、問題ありません。

  4. ここでボイラープレートに落ち込むならば、もう他に試すべきメソッドはありませんので、デフォルト実装の出番です。

  5. もし B <: A ならば、Python は A.__add__ の前に B.__radd__ を試します。これで良い理由は、 A についての知識を持って実装しており、 Complex に委ねる前にこれらのインスタンスを扱えるはずだからです。

A <: Complex と B <: 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):
            # int も含みます。
            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)

# ...