Ответ 1
Использование силового оператора **
будет быстрее, поскольку у него не будет накладных расходов на вызов функции. Вы можете увидеть это, если разобрать код Python:
>>> dis.dis('7. ** i')
1 0 LOAD_CONST 0 (7.0)
3 LOAD_NAME 0 (i)
6 BINARY_POWER
7 RETURN_VALUE
>>> dis.dis('pow(7., i)')
1 0 LOAD_NAME 0 (pow)
3 LOAD_CONST 0 (7.0)
6 LOAD_NAME 1 (i)
9 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
12 RETURN_VALUE
>>> dis.dis('math.pow(7, i)')
1 0 LOAD_NAME 0 (math)
3 LOAD_ATTR 1 (pow)
6 LOAD_CONST 0 (7)
9 LOAD_NAME 2 (i)
12 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
15 RETURN_VALUE
Обратите внимание, что Im использует переменную i
в качестве экспоненты здесь, потому что константные выражения, такие как 7. ** 5
, фактически вычисляются во время компиляции.
Теперь, на практике, это различие не так важно, как вы можете видеть, когда это время:
>>> from timeit import timeit
>>> timeit('7. ** i', setup='i = 5')
0.2894785532627111
>>> timeit('pow(7., i)', setup='i = 5')
0.41218495570683444
>>> timeit('math.pow(7, i)', setup='import math; i = 5')
0.5655053168791255
Итак, в то время как pow
и math.pow
примерно в два раза медленнее, они все еще достаточно быстры, чтобы не беспокоиться. Если вы не сможете на самом деле идентифицировать возведение в степень как узкое место, то не будет причины выбирать один метод над другим, если ясность уменьшается. Это особенно справедливо, поскольку pow
предлагает интегрированную модульную операцию, например.
Альф спросил хороший вопрос в комментариях выше:
timeit
показывает, чтоmath.pow
во всех случаях медленнее, чем**
. Что такоеmath.pow()
хорошо в любом случае? Кто-нибудь знает, где это может быть какое-то преимущество?
Большая разница math.pow
как встроенного pow
, так и силового оператора **
заключается в том, что он всегда использует семантику float. Поэтому, если вы по какой-то причине хотите убедиться, что вы вернете поплавок в результате, тогда math.pow
обеспечит это свойство.
Давайте рассмотрим пример: у нас есть два числа, i
и j
, и понятия не имею, являются ли они поплавками или целыми числами. Но мы хотим получить результат с плавающей точкой i^j
. Итак, какие у нас есть варианты?
- Мы можем преобразовать хотя бы один из аргументов в float, а затем сделать
i ** j
. - Мы можем сделать
i ** j
и преобразовать результат в float (float exponentation автоматически используется, когдаi
илиj
являются float, поэтому результат тот же). - Мы можем использовать
math.pow
.
Итак, давайте протестируем это:
>>> timeit('float(i) ** j', setup='i, j = 7, 5')
0.7610865891750791
>>> timeit('i ** float(j)', setup='i, j = 7, 5')
0.7930400942188385
>>> timeit('float(i ** j)', setup='i, j = 7, 5')
0.8946636625872202
>>> timeit('math.pow(i, j)', setup='import math; i, j = 7, 5')
0.5699394063529439
Как вы можете видеть, math.pow
на самом деле быстрее! И если вы подумаете об этом, накладные расходы от вызова функции также исчезли, потому что во всех остальных альтернативах мы должны называть float()
.
Кроме того, можно отметить, что поведение **
и pow
может быть переопределено путем реализации специального метода __pow__
(и __rpow__
) для настраиваемых типов. Поэтому, если вы этого не хотите (по какой-либо причине), использование math.pow
не сделает этого.