Pourquoi cProfile amène-t-il les fonctions à renvoyer des valeurs différentes ?
-
21-12-2019 - |
Question
Je construis un modèle en Python (3.3.1) pour un contrat assez simple mais délicat avec des flux de trésorerie à long terme.Le modèle complet est assez complexe en termes de temps consommé et j'ai donc décidé d'essayer de le profiler.Cependant, j'obtiens des réponses différentes avec et sans profilage.
J'ai réduit le code à l'exemple suivant :
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)))
Sortir:
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$
Existe-t-il une explication simple pour laquelle cela génère une réponse différente à chaque fois ?J'ai lu le manuel mais je n'y vois rien d'évident.
La solution
Tout d'abord, j'ai essayé de reproduire avec python3, et j'ai exécuté "python3 scratch.py" et "python3 -m cProfile scratch.py" tous deux impriment -20.7933...sur ma machine.
La raison pour laquelle -20.0 est renvoyé sur python 2.x est que l'opérateur de division "/" fonctionnait différemment dans python2.x (http://legacy.python.org/dev/peps/pep-0238/)
En python2, 1/12 == 0
En python3, 1/12 == 0,08333333....
Cela signifie qu'en python2 la ligne
bank.append( bank[t-1] * (1+0.05)**(1/12)
simplifie à
bank.append( bank[t-1] * (1+0.05)**(0)
ou
bank.append( bank[t-1] * 1
Ce n'est probablement pas ce que vous vouliez.L'interprétation python3 est probablement correcte et l'interprétation python2 est assez inutile.En remarque, changer (1/12) en (1.0/12) entraîne une sortie identique sur python2 et python3, et fera en sorte que votre code renvoie la même sortie avec ou sans profilage, mais cela traite le symptôme et non la cause. .
Ma meilleure hypothèse quant à la raison pour laquelle vous obtenez les différentes sorties avec et sans profilage est que vous utilisez python3 sans profilage et python2 avec profilage.Utiliser la même version de Python lorsque vous exécutez votre code avec et sans profilage est essentiel pour obtenir des résultats significatifs.
Le fait que vous utilisiez ./scratch.py indique que vous avez probablement une ligne comme
#!/usr/bin/python3
en haut de scratch.py (bien qu'il ne soit pas inclus dans le code fourni).Quand tu cours
./scratch.py
/usr/bin/python3 est utilisé pour exécuter le fichier
Quand tu cours
python -m cProfile scratch.py
votre interpréteur python par défaut est utilisé pour exécuter le fichier (qui, je suppose, est python2)
Si vous exécutez "python" depuis la ligne de commande (sans aucun autre argument), vous verrez probablement que l'interpréteur par défaut est 2.X.
Ainsi, pour que votre code renvoie une sortie identique avec et sans profilage, il devrait être aussi simple que de spécifier python3 lors du profilage :
python3 -m cProfile scratch.py