9
votes

timeit ValueError: stmt n'est ni une chaîne ni appelable

J'ai joué avec timeit en Python, j'ai eu un problème étrange.

Je définis une fonction simple add . timeit fonctionne lorsque je passe add deux paramètres de chaîne. Mais cela déclenche ValueError: stmt n'est ni une chaîne ni appelable quand je passe add deux paramètres int .

>>> import timeit
>>> def add(x,y):
...     return x + y
... 


>>> a = '1'
>>> b = '2'
>>> timeit.timeit(add(a,b))
0.01355926995165646


>>> a = 1
>>> b = 2
>>> timeit.timeit(add(a,b))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/anaconda/lib/python3.6/timeit.py", line 233, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/anaconda/lib/python3.6/timeit.py", line 130, in __init__
    raise ValueError("stmt is neither a string nor callable")
ValueError: stmt is neither a string nor callable

Pourquoi le type de paramètre est-il important ici?


1 commentaires

Pourquoi le type de l'objet n'a-t-il pas d'importance?


4 Réponses :


6
votes

Ma question est de savoir pourquoi le type de paramètre est important ici?

Les arguments de fonction sont entièrement évalués avant l'appel de la fonction. Cela signifie que lorsque vous faites:

timeit.timeit(add(a,b))

Ensuite, add (a, b) a déjà été calculé avant que timeit ne soit utilisé . Donc, ça n'a rien à voir avec le temps.

La raison pour laquelle timeit.timeit (add (a, b)) "fonctionne" quand a et b sont des chaînes numériques est juste ridicule : il chronomètre l'évaluation de '12' . Le résultat de l'appel de add ('1', '2') se trouve être une chaîne valide de code Python ici. timeit le compile et suppose que vous vouliez chronométrer l'évaluation de l'entier littéral 12.


6 commentaires

merci pour votre réponse généreuse! Cela signifie-t-il que passer add («1», «2») équivaut au code suivant? timeit.timeit ("add (1,2)", "from main import add")


Comment l'OP doit-il modifier le code afin que le temps d'évaluation de la somme des entiers puisse être mesuré?


@liangli Pas exactement. Mais c'est une façon correcte de chronométrer l'exécution réelle de add en utilisant des arguments entiers.


@liangli: non, c'est la valeur de retour de add () qui est passée à timeit . add ('1', '2') renvoie la chaîne '12' , c'est donc l'équivalent de timeit ('12 ') .


puisque add (a, b) passé n'est pas une chaîne, je suis curieux de savoir ce qui se passe sous le capot. disons, pourquoi timeit ne prend-il pas add (1,2) comme chaîne valide de code python?


@liangli: add (1, 2) n'est pas une chaîne , c'est une expression qui renvoie un résultat.



14
votes

Votre erreur est de supposer que Python transmet l'expression add (a, b) à timeit () . Ce n'est pas le cas, add (a, b) n'est pas une chaîne, c'est une expression donc Python à la place exécute add (a, b) code > et le résultat de cet appel est transmis à l'appel timeit () .

Donc pour add ('1', '2') code> le résultat est '12' , une chaîne. Passer une chaîne à timeit () est très bien. Mais add (1, 2) est 12 , un entier. timeit (12) vous donne une exception. Non pas que le timing '12' soit tout ce qui est intéressant, bien sûr, mais c'est une expression Python valide produisant la valeur entière 12:

>>> import timeit
>>> def add(x, y):
...     return x + y
...
>>> a = '1'
>>> b = '2'
>>> timeit.timeit('add(a, b)', 'from __main__ import add, a, b')
0.16069997000158764
>>> a = 1
>>> b = 2
>>> timeit.timeit('add(a, b)', 'from __main__ import add, a, b')
0.10841095799696632

C'est tout parfaitement normal; sinon, comment pourriez-vous passer directement le résultat d'une fonction à une autre fonction? timeit.timeit () est juste une autre fonction Python , rien de si spécial que cela désactivera l'évaluation normale des expressions.

Ce que vous voulez, c'est pour passer une chaîne avec l'expression à timeit () . timeit () n'a pas accès à votre fonction add () , ni à a ou b , vous besoin de lui donner accès avec le deuxième argument, la chaîne de configuration. Vous pouvez utiliser de __main__ import add, a, b pour importer l'objet de fonction add :

timeit.timeit('add(a, b)', 'from __main__ import add, a, b')

Vous obtenez maintenant des résultats plus significatifs:

>>> import timeit
>>> def add(x, y):
...     return x + y
...
>>> a = '1'
>>> b = '2'
>>> add(a, b)
'12'
>>> timeit.timeit('12')
0.009553937998134643
>>> a = 1
>>> b = 2
>>> add(a, b)
3
>>> timeit.timeit(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../lib/python3.7/timeit.py", line 232, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/.../lib/python3.7/timeit.py", line 128, in __init__
    raise ValueError("stmt is neither a string nor callable")
ValueError: stmt is neither a string nor callable

L'ajout d'entiers est donc plus rapide que l'ajout de chaînes. Vous voudrez probablement essayer ceci avec différentes tailles d'entiers et de chaînes, mais l'ajout d'entiers restera le résultat le plus rapide.


0 commentaires

0
votes

une autre façon de résoudre le type "problème" est de passer le résultat de la fonction sous forme de chaîne!

timeit.timeit('%s'%(add(1,2)))
or
timeit.timeit(f'{add(1,2)}')


0 commentaires

2
votes

Avec la version chaîne, add renvoie une chaîne, que timeit peut évaluer. Donc "12" est une expression python valide alors que 3 ne l'est pas.

timeit.timeit(lambda: add(1,2))

La meilleure façon d'utiliser timeit, est de déformer la fonction que vous voulez tester avec un lambda:

XXX

C'est beaucoup plus élégant que de jouer avec des chaînes. Je ne comprends vraiment pas pourquoi tous les exemples utilisent des chaînes.


0 commentaires