Документация Python неофициальный перевод
Содержание страницы

9.1. numbers – Числовые абстрактные базовые классыnumbers – Numeric abstract base classes

The numbers module (PEP 3141) defines a hierarchy of numeric abstract base classes which progressively define more operations. None of the types defined in this module can be instantiated.

class numbers.Number

Корень числовой иерархии. Если нужно просто проверить, является ли аргумент x числом, не уточняя каким, используйте isinstance(x, Number).

9.1.1. Числовая башняThe numeric tower

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().

class numbers.Rational

Является подтипом Real и добавляет свойства numerator и denominator, которые должны быть представлены в несократимом виде. С их помощью он предоставляет реализацию по умолчанию для float().

numerator

Абстрактный.

denominator

Абстрактный.

class numbers.Integral

Является подтипом Rational и добавляет преобразование в int. Предоставляет реализации по умолчанию для float(), numerator и denominator. Добавляет абстрактные методы для ** и побитовых операций: <<, >>, &, ^, |, ~.

9.1.2. Примечания для реализаторов типовNotes for type implementors

Реализаторам следует соблюдать осторожность, чтобы равные числа были равны и имели одинаковые хеш-значения. Это может быть нетривиально, если существует два разных расширения вещественных чисел. Например, fractions.Fraction реализует hash() следующим образом:

def __hash__(self):
    if self.denominator == 1:
        # Получить целые числа правильно.
        return hash(self.numerator)
    # Дорогостоящая проверка, но определённо корректная.
    if self == float(self):
        return hash(float(self))
    else:
        # Использовать хеш кортежа, чтобы избежать высокой частоты коллизий для
        # простых дробей.
        return hash((self.numerator, self.denominator))

9.1.2.1. Добавление других числовых ABCAdding More Numeric ABCs

Конечно, существует больше возможных абстрактных базовых классов для чисел, и эта иерархия была бы плохой, если бы она исключала возможность их добавления. Можно добавить MyFoo между Complex и Real следующим образом:

class MyFoo(Complex): ...
MyFoo.register(Real)

9.1.2.2. Реализация арифметических операцийImplementing the arithmetic operations

Необходимо реализовать арифметические операции так, чтобы операции со смешанными типами либо вызывали реализацию, автор которой знал о типах обоих аргументов, либо преобразовывали оба аргумента к ближайшему встроенному типу и выполняли операцию там. Для подтипов 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 будет экземпляром A, который является подтипом Complex (a : A <: Complex), и b : B <: Complex. Будем рассматривать a + b:

  1. Если A определяет __add__(), который принимает b, то всё в порядке.
  2. Если A возвращается к шаблонному коду, и если он возвращает значение из __add__(), то мы упускаем возможность, что B определяет более разумную __radd__(), поэтому шаблонный код должен возвращать NotImplemented из __add__(). (Или A может вообще не реализовывать __add__().)
  3. Тогда B's __radd__() получает шанс. Если он принимает a, всё в порядке.
  4. Если он возвращается к шаблонному коду, других возможных методов для попытки нет, поэтому именно здесь должна находиться реализация по умолчанию.
  5. Если B <: A, Python сначала пробует B.__radd__, а затем A.__add__. Это нормально, потому что он был реализован со знанием о 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):
            # Включает целые числа.
            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)

# ...