classification
Title: supplying an empty string to timeit causes an IndentationError
Type: Stage: patch review
Components: Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: DahlitzFlorian, anthonypjshaw, edison.abahurire, remi.lapeyre, serhiy.storchaka, terry.reedy
Priority: normal Keywords: patch

Created on 2020-05-18 12:44 by edison.abahurire, last changed 2020-05-24 11:05 by terry.reedy.

Pull Requests
URL Status Linked Edit
PR 20286 open DahlitzFlorian, 2020-05-21 08:25
Messages (9)
msg369208 - (view) Author: Edison Abahurire (edison.abahurire) * Date: 2020-05-18 12:44
The Error can be evidenced below:

```
>>>
>>> import timeit
>>>
>>> timeit.timeit('', number=10000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.8/timeit.py", line 232, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/usr/lib/python3.8/timeit.py", line 131, in __init__
    code = compile(src, dummy_src_name, "exec")
  File "<timeit-src>", line 7
    _t1 = _timer()
    ^
IndentationError: expected an indented block
>>>
```
msg369240 - (view) Author: Rémi Lapeyre (remi.lapeyre) * Date: 2020-05-18 15:15
Is this different than what you would expect?

Supplying garbage to timeit will result in an error:

>>> from timeit import timeit
>>> timeit('weofinwofinwe')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/timeit.py", line 232, in timeit
    return Timer(stmt, setup, timer, globals).timeit(number)
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/timeit.py", line 176, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
NameError: name 'weofinwofinwe' is not defined


If you want to time an empty loop, you can use:

>>> timeit('pass', number=10000)
0.0001043230000021822
msg369466 - (view) Author: Edison Abahurire (edison.abahurire) * Date: 2020-05-20 16:37
Yes, This is unexpected

In the first case:
Supplying an empty string can also mean no code has been supplied. So it could be better treated as 'pass'.

In the second case:
 Error messages are meant to inform you of what you have done wrong so that you fix it. I think raising an Indentation error is far from what a user who is calling that method would be expecting, it does not help them at-all.

In the third case:
Supplying an empty string to timeit directly in bash ```python -m timeit '' ``` raises no such error. so we could say that fixing this will align the behavior in the two ways the function is run to be the same.
msg369503 - (view) Author: Florian Dahlitz (DahlitzFlorian) * Date: 2020-05-21 08:02
Calling timeit from command-line with the empty string defaults to 'pass'. I suggest to adopt this behaviour for calling timeit.timeit in the REPL as @edison.abahurire already suggested. I would be happy to submit a PR for it.
msg369506 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2020-05-21 08:29
Accepting an empty string in CLI is just an artifact of the implementation.  There was no intention to support it. It will fail if pass a space:

    ./python -m timeit ' '

or two empty arguments:
IndentationError:
    ./python -m timeit '' ''

I do not see this is an issue. Garbage in -- garbage out. IndentationError is a subclass of SyntaxError, so if you handle it programmatically, it does not matter.

Of course we try to catch some user errors and provide informative traceback. timeit now correctly handles most of code which interferes with the control flow in functions and loops: 'return', 'yield', 'break', 'await'.

But it is still possible to bypass the validation. For example:

    ./python -m timeit -s 'while False:' -s '    pass' '    break'

I think there is an infinite number of ways to fool timeit. And an empty string is just one of them, not special enough to add a special handling in the code.
msg369507 - (view) Author: Florian Dahlitz (DahlitzFlorian) * Date: 2020-05-21 09:10
I see your point and agree with you. However, IMHO the CLI and the direct function call should behave the same way to not confuse users. The opened PR ensures that.
msg369693 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-05-23 06:02
Let a wcs be a string consisting of only whitespace and comments.
compile(wcs, '', 'exec') treats wcs the same as 'pass'.
Hence, a file with only whitespace and comments is the same as 'pass'.
compile(wcs, '', 'single'), for whatever reason, raises
SyntaxError: unexpected EOF while parsing
To get around this, a wcs input into IDLE's Shell, compiles with 'single', is replaced with 'pass' in codeop._maybe_compile, line 76.  I presume the REPL does the same.

If one thinks of parameter stmt as a top-level statement (or statements), it is reasonable to expect '' to be the same as 'pass'.  If one knows that stmt will be embedded into a compound statement (whether 'while', 'for' or 'def' does not matter here) for repeated execution, then 'pass' is more obviously the minimal statement.

It would have been better if the parameter name were 'suite', as suites can never be only whitespace.  We cannot change this, but I suggest replacing

The constructor takes a statement to be timed, an additional statement used for setup, and a timer function. Both statements default to 'pass'; the timer function is platform-dependent (see the module doc string). stmt and setup may also contain multiple statements separated by ; or newlines, as long as they don’t contain multi-line string literals. 

with the shorter, clearer, and updated

The constructor takes suite of statement to be timed, an additional suite used for setup, and a timer function (default time.perf_counter). Both suites default to 'pass' and may not contain multi-line string literals.

Since 3.3, the default timer is platform-independent, at least from a user viewpoint, and not mentioned in timeit.__doc__.  Suites can always have multiple statments separated by ; and \n.  The only needed qualification is the default and the restriction.
msg369728 - (view) Author: Florian Dahlitz (DahlitzFlorian) * Date: 2020-05-23 16:32
@terry.reedy sorry if I misunderstood you, but it seems to me that you agree with the proposed changes?
msg369782 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2020-05-24 11:05
I intended to say that the current behavior would be less puzzling if the doc were changed as I suggest.  I think that a doc change would be sufficient.
History
Date User Action Args
2020-05-24 11:05:49terry.reedysetmessages: + msg369782
2020-05-23 16:32:53DahlitzFloriansetmessages: + msg369728
2020-05-23 07:28:07terry.reedysetversions: + Python 3.10
2020-05-23 06:02:33terry.reedysetnosy: + terry.reedy
messages: + msg369693
2020-05-22 07:44:17rahul-kumisetnosy: - rahul-kumi
2020-05-21 09:10:55DahlitzFloriansetmessages: + msg369507
2020-05-21 08:29:09serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg369506
2020-05-21 08:25:59DahlitzFloriansetkeywords: + patch
stage: patch review
pull_requests: + pull_request19560
2020-05-21 08:02:50DahlitzFloriansetmessages: + msg369503
2020-05-21 07:08:33DahlitzFloriansetnosy: + DahlitzFlorian
2020-05-20 16:37:05edison.abahuriresetmessages: + msg369466
2020-05-18 16:40:23rahul-kumisetnosy: + rahul-kumi
2020-05-18 15:15:08remi.lapeyresetnosy: + remi.lapeyre
messages: + msg369240
2020-05-18 12:44:21edison.abahurirecreate