Por que cProfile faz com que as funções retornem valores diferentes?
-
21-12-2019 - |
Pergunta
Estou construindo um modelo em Python (3.3.1) para um contrato bastante simples, mas complicado, com fluxos de caixa de longo prazo.O modelo completo é bastante complexo em termos de tempo consumido e por isso decidi tentar traçar um perfil dele.No entanto, estou recebendo respostas diferentes com e sem criação de perfil.
Reduzi o código ao seguinte exemplo:
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)))
Saída:
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 uma explicação simples de por que isso gera uma resposta diferente a cada vez?Eu li o manual, mas não vejo nada óbvio.
Solução
Primeiro, tentei reproduzir com python3 e executar "python3 scratch.py" e "python3 -m cProfile scratch.py" ambos imprimem -20.7933...na minha máquina.
A razão pela qual -20.0 é retornado em python 2.x é porque o operador de divisão "/" funcionou de maneira diferente em python2.x (http://legacy.python.org/dev/peps/pep-0238/)
Em python2, 1/12 == 0
Em python3, 1/12 == 0,08333333....
Isso significa que em python2 a linha
bank.append( bank[t-1] * (1+0.05)**(1/12)
simplifica para
bank.append( bank[t-1] * (1+0.05)**(0)
ou
bank.append( bank[t-1] * 1
Provavelmente não foi isso que você pretendia.A interpretação python3 provavelmente está correta e a interpretação python2 é bastante inútil.Como observação lateral, alterar (1/12) para (1.0/12) resulta em saída idêntica em python2 e python3 e fará com que seu código retorne a mesma saída com ou sem criação de perfil, mas isso trata o sintoma e não a causa .
Meu melhor palpite sobre por que você está obtendo resultados diferentes com e sem criação de perfil é que você está usando python3 sem criação de perfil e python2 com criação de perfil.Usar a mesma versão do python ao executar seu código com e sem criação de perfil é essencial para obter resultados significativos.
O fato de você estar usando ./scratch.py indica que você provavelmente tem uma linha como
#!/usr/bin/python3
no topo de scratch.py (embora não esteja incluído no código fornecido).Quando você corre
./scratch.py
/usr/bin/python3 é usado para executar o arquivo
Quando você corre
python -m cProfile scratch.py
seu interpretador python padrão é usado para executar o arquivo (que suponho ser python2)
Se você executar "python" na linha de comando (sem quaisquer outros argumentos), provavelmente verá que o interpretador padrão é 2.X.
Portanto, fazer com que seu código retorne uma saída idêntica com e sem criação de perfil deve ser tão simples quanto especificar python3 ao criar o perfil:
python3 -m cProfile scratch.py