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

抽象プロパティ。 この有理数の分子。

denominator

抽象プロパティ。 この有理数の分母。

class numbers.Integral

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

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

実装するときは、等しい数が等しいと比較され、同じハッシュ値を持つよう注意してください。 実数を異なる方法で拡張した型が 2 つある場合、この扱いは難しいことがあります。 数値型のハッシュ化 も参照してください。

さらに数の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)

# ...