9.1. numbers --- 数の抽象基底クラス¶
バージョン 2.6 で追加.
numbers モジュール (PEP 3141) は数の 抽象基底クラス の、順により多くの演算を定義していく階層を定義します。このモジュールで定義される型はどれもインスタンス化できません。
-
class
numbers.Number¶ 数の階層の根。引数 x が、種類は何であれ、数であるということだけチェックしたい場合、
isinstance(x, Number)が使えます。
9.1.1. 数値塔¶
-
class
numbers.Complex¶ この型のサブクラスは複素数を表し、組み込みの
complex型を受け付ける演算を含みます。それらは:complexおよびboolへの変換、real,imag,+,-,*,/,abs(),conjugate(),==,!=です。-と!=以外の全てのものは抽象メソッドや抽象プロパティです。-
real¶ 抽象プロパティ。この数の実部を取り出します。
-
imag¶ 抽象プロパティ。この数の虚部を取り出します。
-
conjugate()¶ 抽象プロパティ。複素共役を返します。たとえば、
(1+3j).conjugate() == (1-3j)です。
-
-
class
numbers.Real¶ Complexの上に、Realは実数で意味を成す演算を加えます。簡潔に言うとそれらは:
floatへの変換,math.trunc(),round(),math.floor(),math.ceil(),divmod(),//,%,<,<=,>および>=です。Real はまた
complex(),real,imagおよびconjugate()のデフォルトを提供します。
9.1.2. 型実装者のための注意事項¶
実装する人は等しい数が等しく扱われるように同じハッシュを与えるように気を付けねばなりません。これは二つの異なった実数の拡張があるような場合にはややこしいことになるかもしれません。たとえば、 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))
9.1.2.1. さらに数のABCを追加する¶
もちろん、他にも数に対する ABC が有り得ますし、そういったものを付け加える可能性を閉ざしてしまうとすれば貧相な階層でしかありません。たとえば MyFoo を Complex と Real の間に付け加えるには:
class MyFoo(Complex): ...
MyFoo.register(Real)
9.1.2.2. 算術演算の実装¶
私たちは、混在型(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, long, 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)
# ...
