Почему cProfile приводит к тому, что функции возвращают разные значения?
-
21-12-2019 - |
Вопрос
Я создаю модель на Python (3.3.1) для довольно простого, но сложного контракта с долгосрочными денежными потоками.Полная модель довольно сложна с точки зрения затрат времени, и поэтому я решил попробовать ее профилировать.Тем не менее, я получаю разные ответы с профилированием и без него.
Я сократил код до следующего примера:
def generate_cashflows( income ):
contingent_expense = [1000.0]
income_cf = [income]
outgo_cf = [ -0.001 * contingent_expense[0] ]
bank = [ income_cf[0] + outgo_cf[0] ]
for t in range(1, 20):
contingent_expense.append(1000.0)
income_cf.append( income )
outgo_cf.append( -contingent_expense[t] * 0.001 )
bank.append( bank[t-1] * (1+0.05)**(1/12)
+ income_cf[t]
+ outgo_cf[t]
)
return bank[-1]
print(str(generate_cashflows(0)))
Выходной сигнал:
calum@calum:~/pricing/model$ ./scratch.py
-20.793337746348953
calum@calum:~/pricing/model$ python -m cProfile scratch.py
-20.0
80 function calls in 0.000 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 scratch.py:5(<module>)
1 0.000 0.000 0.000 0.000 scratch.py:5(generate_cashflows)
76 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.000 0.000 {range}
calum@calum:~/pricing/model$
Есть ли простое объяснение, почему это каждый раз выдает разный ответ?Я ознакомился с руководством, но не вижу ничего очевидного.
Решение
Сначала я попытался воспроизвести с помощью python3 и, запустив "python3 scratch.py" и "python3 -m cProfile scratch.py", напечатал -20.7933...на моей машине.
Причина, по которой -20.0 возвращается в python 2.x, заключается в том, что оператор разделения "/" работал по-другому в python2.x (http://legacy.python.org/dev/peps/pep-0238/)
В python2 1/12 == 0
В python3 1/12 == 0.08333333....
Это означает, что в python2 строка
bank.append( bank[t-1] * (1+0.05)**(1/12)
упрощает до
bank.append( bank[t-1] * (1+0.05)**(0)
или
bank.append( bank[t-1] * 1
Вероятно, это не то, что вы предполагали.Интерпретация python3, вероятно, верна, а интерпретация python2 довольно бесполезна.В качестве дополнительного примечания, изменение (1/12) на (1.0 / 12) приводит к идентичному выводу как на python2, так и на python3 и заставит ваш код возвращать один и тот же результат с профилированием или без него, но это лечит симптом, а не причину.
Мое лучшее предположение относительно того, почему вы получаете разные выходные данные с профилированием и без него, заключается в том, что вы используете python3 без профилирования и python2 с профилированием.Использование одной и той же версии python при выполнении вашего кода с профилированием и без него важно для получения значимых результатов.
Тот факт, что вы используете ./scratch.py указывает на то, что у вас, вероятно, есть строка типа
#!/usr/bin/python3
вверху scratch.py (хотя это не включено в предоставленный код).Когда ты бежишь
./scratch.py
для выполнения файла используется /usr/bin/python3
Когда ты бежишь
python -m cProfile scratch.py
ваш интерпретатор python по умолчанию используется для выполнения файла (который, я предполагаю, является python2).
Если вы запустите "python" из командной строки (без каких-либо других аргументов), вы, вероятно, увидите, что intepreter по умолчанию равен 2.X.
Таким образом, заставить ваш код возвращать идентичные выходные данные с профилированием и без него должно быть так же просто, как указать python3 при профилировании:
python3 -m cProfile scratch.py