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()のデフォルトも提供します。The
numeratoranddenominatorvalues should be instances ofIntegraland should be in lowest terms withdenominatorpositive.-
numerator¶ 抽象プロパティ。
-
denominator¶ 抽象プロパティ。
-
型実装者のための注意事項¶
実装する人は等しい数が等しく扱われるように同じハッシュを与えるように気を付けねばなりません。これは二つの異なった実数の拡張があるような場合にはややこしいことになるかもしれません。たとえば、 fractions.Fraction は hash() を以下のように実装しています:
def __hash__(self):
if self.denominator == 1:
# Get integers right.
return hash(self.numerator)
# Expensive check, but definitely correct.
if self == float(self):
return hash(float(self))
else:
# Use tuple's hash to avoid a high collision rate on
# simple fractions.
return hash((self.numerator, self.denominator))
さらに数のABCを追加する¶
数に対する ABC が他にも多く存在しうることは、言うまでもありません。それらの ABC を階層に追加する可能性が閉ざされるとしたら、その階層は貧相な階層でしかありません。たとえば、 MyFoo を Complex と Real の間に付け加えるには、次のようにします:
class MyFoo(Complex): ...
MyFoo.register(Real)
算術演算の実装¶
算術演算を実装する際には、型混合(mixed-mode)演算を行うと、作者が両方の引数の型について知っているような実装を呼び出すか、両方の引数をそれぞれ最も似ている組み込み型に変換してその型で演算を行うか、どちらになるのが望ましい実装です。つまり、 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 です。
ほとんどの演算はどのような型についても非常に良く似ていますので、与えられた演算子について順結合(forward)および逆結合(reverse)のメソッドを生成する支援関数を定義することは役に立ちます。たとえば、 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, numbers.Real):
return fallback_operator(float(a), float(b))
elif isinstance(a, numbers.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)
# ...