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¶
抽象プロパティ。 この有理数の分母。
型を実装する人向けの注意事項¶
実装するときは、等しい数が等しいと比較され、同じハッシュ値を持つよう注意してください。 実数を異なる方法で拡張した型が 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 を考えます:
Aがbを受け付ける__add__()を定義していれば、問題ありません。Aがボイラープレートにフォールバックするときに__add__()から値を返してしまうと、Bがもっと適切な__radd__()を定義している可能性を見落とすため、ボイラープレートの__add__()はNotImplementedを返すべきです。 (あるいは、Aが__add__()をまったく実装しないこともできます。)次に
Bの__radd__()に機会が回ってきます。 これがaを受け付ければ、問題ありません。ここでボイラープレートに落ち込むならば、もう他に試すべきメソッドはありませんので、デフォルト実装の出番です。
もし
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)
# ...