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.
型を実装する人向けの注意事項¶
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 を考えます:
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)
# ...