Ответ 1
Быстрая проверка
Из сигнатур мы можем сказать, что они разные:
pow (x, y [, z])
math.pow(x, y)
Кроме того, попробовав его в оболочке, вы получите краткое описание:
>>> pow is math.pow
False
Проверка различий
Еще один способ понять различия в поведении между двумя функциями - проверить их:
import math
import traceback
import sys
inf = float("inf")
NaN = float("nan")
vals = [inf, NaN, 0.0, 1.0, 2.2, -1.0, -0.0, -2.2, -inf, 1, 0, 2]
tests = set([])
for vala in vals:
for valb in vals:
tests.add( (vala, valb) )
tests.add( (valb, vala) )
for a,b in tests:
print("math.pow(%f,%f)"%(a,b) )
try:
print(" %f "%math.pow(a,b))
except:
traceback.print_exc()
print("__builtins__.pow(%f,%f)"%(a,b) )
try:
print(" %f "%__builtins__.pow(a,b))
except:
traceback.print_exc()
Затем мы можем заметить некоторые тонкие различия. Например:
math.pow(0.000000,-2.200000)
ValueError: math domain error
__builtins__.pow(0.000000,-2.200000)
ZeroDivisionError: 0.0 cannot be raised to a negative power
Существуют и другие различия, и приведенный выше тестовый список не является полным (без длинных чисел, без сложностей и т.д.), но это даст нам прагматичный список того, как две функции ведут себя по-разному. Я также рекомендовал бы расширить вышеуказанный тест, чтобы проверить тип, возвращаемый каждой функцией. Вероятно, вы могли бы написать нечто подобное, что создаст отчет о различиях между двумя функциями.
math.pow()
math.pow()
обрабатывает свои аргументы совсем иначе, чем встроенные **
или pow()
. Это происходит за счет гибкости. Посмотрев источник, мы можем видеть, что аргументы math.pow()
передаются непосредственно в double:
static PyObject *
math_pow(PyObject *self, PyObject *args)
{
PyObject *ox, *oy;
double r, x, y;
int odd_y;
if (! PyArg_UnpackTuple(args, "pow", 2, 2, &ox, &oy))
return NULL;
x = PyFloat_AsDouble(ox);
y = PyFloat_AsDouble(oy);
/*...*/
Затем проверки выполняются против удвоений для достоверности, а затем результат передается в базовую библиотеку C math.
встроенный pow()
С другой стороны, встроенный pow()
(то же, что и оператор **
) ведет себя по-разному, он фактически использует собственную реализацию объектов оператора **
, которая может быть переопределена конечным пользователем, если необходимо заменить на метод __pow__()
, __rpow__()
или __ipow__()
.
Для встроенных типов поучительно изучать разницу между функцией мощности, реализованной для двух числовых типов, например floats, long и complex.
Переопределение поведения по умолчанию
Эмулирующие числовые типы описаны здесь. по сути, если вы создаете новый тип чисел с неопределенностью, вам нужно будет предоставить методы __pow__()
, __rpow__()
и, возможно, __ipow__()
для вашего типа. Это позволит использовать ваши номера с оператором:
class Uncertain:
def __init__(self, x, delta=0):
self.delta = delta
self.x = x
def __pow__(self, other):
return Uncertain(
self.x**other.x,
Uncertain._propagate_power(self, other)
)
@staticmethod
def _propagate_power(A, B):
return math.sqrt(
((B.x*(A.x**(B.x-1)))**2)*A.delta*A.delta +
(((A.x**B.x)*math.log(B.x))**2)*B.delta*B.delta
)
Чтобы переопределить math.pow()
, вам нужно будет обезвредить его для поддержки вашего нового типа:
def new_pow(a,b):
_a = Uncertain(a)
_b = Uncertain(b)
return _a ** _b
math.pow = new_pow
Обратите внимание, что для этого вам придется бороться с классом Uncertain
, чтобы справиться с экземпляром Uncertain
в качестве входа в __init__()