Skip to content

[doc] timeit execution enviroment #42765

Open

Description

@rurpy
mannequin
BPO 1397474
Nosy @freddrake, @rhettinger, @terryjreedy
Files
  • issue1397474.py27.1.patch
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2006-01-05.03:50:10.000>
    labels = ['type-bug', '3.8', '3.9', '3.10', 'docs']
    title = '[doc] timeit execution enviroment'
    updated_at = <Date 2020-11-08.23:26:02.092>
    user = 'https://bugs.python.org/rurpy'

    bugs.python.org fields:

    activity = <Date 2020-11-08.23:26:02.092>
    actor = 'iritkatriel'
    assignee = 'docs@python'
    closed = False
    closed_date = None
    closer = None
    components = ['Documentation']
    creation = <Date 2006-01-05.03:50:10.000>
    creator = 'rurpy'
    dependencies = []
    files = ['18871']
    hgrepos = []
    issue_num = 1397474
    keywords = ['patch']
    message_count = 6.0
    messages = ['27252', '85687', '115276', '116330', '126026', '141038']
    nosy_count = 8.0
    nosy_names = ['fdrake', 'rhettinger', 'terry.reedy', 'rurpy', 'LambertDW', 'eli.bendersky', 'docs@python', 'rurpy2']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue1397474'
    versions = ['Python 3.8', 'Python 3.9', 'Python 3.10']

    Linked PRs

    Activity

    1. rurpy commented on Jan 5, 2006

      rurpymannequin
      MannequinAuthor

      library reference manual, section 10.10

      The documentation for the timeit module
      does not make clear exactly what enviroment
      the timed code will be run in, particularly
      when a function in the current program is
      being timed (as opposed to an external
      program.)

      This information is available in the examples
      section but examples should illustrate already
      described behavior, not present new information.

      I think the following text should be appended
      below the third paragraph in the "class Timer"
      section which reads:

      To measure the execution time of the first
      statement, use the timeit() method. The
      repeat() method is a convenience to call
      timeit() multiple times and return a list
      of results.

      Proposed addition:

      The timed statement is executed in the namespace
      of the timeit module. If a function in the
      __main__ module is being timed, it can
      be made accessible to the timer module by
      using a setup statement like
      "from __main__ import xx"
      where xx is the function's name in __main__.
      Of course "__main__" can be a different module
      name if appropriate.

    2. added
      docsDocumentation in the Doc dir
      on Jan 5, 2006
    3. added
      docsDocumentation in the Doc dir
      on Jan 5, 2006
    4. rhettinger commented on Apr 7, 2009

      @rhettinger
      Contributor

      See related discussion in bpo-5441 and bpo-2527.

    5. removed their assignment
      on Apr 7, 2009
    6. terryjreedy commented on Aug 31, 2010

      @terryjreedy
      Member

      I agree that the Timer doc is deficient in not saying that timing is done within a function defined within the timeit module. It is also deficient in not mentioning the secret of how to successfully pass user-defined functions until the very bottom instead of where that option is described. (I had missed this very point until recently.)

      The discussion of possible new features for 3.2 (more likely later) does not affect 2.7/3.1 and should not stop a change in 3.2 either. I propose that the Timer doc be revised to the following:

      PROPOSED REPLACEMENT
      '''
      Class for timing execution speed of small code snippets. The constructor creates a function that executes the *setup* statement once and then times some number of executions of the *stmt* statement (see Timer.timeit). Both statements default to 'pass'. The *timer* parameter defaults to a platform-dependent timer function (see the module doc string). Both *stmt* and *setup* may contain multiple statements separated by ; or newlines as long as they don’t contain multi-line string literals.

      Both *stmt* and *setup* can also be objects that are callable without arguments. Passing testfunc rather than 'testfunc()' may reduce the timing overhead. However, if testfunc is a Python function, passing its quoted code should have even less overhead because doing so eliminates an extra function call.

      To give *stmt* (whether it is a callable name or code string) access to pre-defined user objects, such as testfunc, *setup* must include an import, such as 'from __main__ import testfunc'. Note that 'from __main__ import *' does not work because * imports are not legal within functions.

      To measure the execution time of *stmt*, use the timeit() method. The repeat() method is a convenience to call timeit() multiple times and return a list of results.
      '''

      Note 1. testfunc , 'testfunc()' , and 'from ....' should be marked up as code. Perhaps the first two should be double quoted also, depending of the style convention. What must be clear is the difference between passing an unquoted function name and a string.

      Note 2. The 'may reduce' comment: timeit.timeit(str) (for instance) runs noticeably faster than timeit.timeit('str()'). I presume this is because callables get bound to a local name and local name lookup is faster than builtin lookup. This difference does not apply to imported user names. The existing statement "Note that the timing overhead is a little larger in this case because of the extra function calls." is confusing to me because it does not specify the alternative to 'this case' and there are two possibilities, which I specified.

      Note 3. The comment about * imports should be deleted for 2.7 version.

      ADDITIONAL CHANGE
      Add the following to the very bottom as part of the final example:
      "
      t = Timer(test, "from __main__ import test")
      print(t.timeit()) # should be nearly the same
      "

    7. elibendersky commented on Sep 13, 2010

      elibenderskymannequin
      Mannequin

      Terry,

      I'm attaching a patch for 2.7, however it's more proof-of-concept than final, because I have a few comments. The patch generally implements your documentation suggestion without the import * warning and without adding the final example. The latter for a couple of reasons:

      1. My tests show that passing the callable instead of the string 'test()' actually takes longer to run.

      2. When the callable is passed, the setup of importing 'test' from '__main__' is necessary, because the timed code doesn't have to lookup the 'test' symbol. If you want to leave that example in, the setup string can be ditched.

      Let me know your thoughts and I will update the patch.

    8. rurpy2 commented on Jan 11, 2011

      rurpy2mannequin
      Mannequin

      I find the changes suggested by T Reedy and refined in the
      patch by E Bendersky an improvement. However, I found the
      following things confused me when reading it:

      "...constructor creates a function..."
      the constructor creates a Timeit instance, not a function.
      There is a method on that instance that is the function but
      the way it is phrased in like describing the autos coming off
      a Ford production line as "steering wheels". As written, the
      statement creates an immediate WTF reaction in my mind.

      "...that executes the *setup* statement..."
      Use of term "statement" here is confusing as that term already
      has a well defined meaning in Python docs. One can't syntactically
      use a statement as a function argument. Only by suspending this
      confusion and reading further does one discover that "statement"
      here means a string containing text of some python code or a
      callable object.

      Use of "statement" (singular) also directly conflicts with following
      information that states multiple statements are ok.

      Since the synopsis line already refers to "snippets", I think
      continuing to use that is better (having no preexisting conflicting
      meanings) than "statement".

      "...default to ``'pass'`..."
      The call summary immediately above makes clear what the default
      parameter values are. While fine to repeat it in text, it is not
      high priority information so should be moved to later in the
      description.

      "...or newlines as long as they don’t contain multi-line string literals..."
      What is a multi-line string literal? The Language Reference ->
      Lexical Analysis -> String Literals section says nothing about
      "multi-line literals".
      Is it "a\nb"? Or """a
      b"""? Both?
      '"a\nb"' actually works. '"""a
      b"""' doesn't of course but it is it is also clearly not valid
      python string syntax so I'm not sure that 'multi-line strings need
      even be mentioned. If it is mentioned then it should be made clear
      that multi-line string literals are not allowed not because timeit
      doesn't like them, but because python syntax allows no way to
      embed them in another string.
      .
      "...pre-defined user objects..."
      What does "pre-defined" mean? Builtin? Imported from stdlib?
      I would use a more explicit description here.

      I also think a short explanation of *why* one needs to import
      program objects in 'setup' makes it a little easier and quicker
      to understand what one is doing with the import, particularly if
      one is using timeit somewhere other than __main__.. Thus I
      suggest expanding that section slightly.

      Here is my attempt to adjust taking the above observations into
      account. (Sorry, can't supply a patch since I have slow internet
      connection and don't have source. Text below is just my hand edit
      of the "+" lines in Eli's patch.)

      Class for timing execution speed of small code snippets.
      A Timeit instance will contain a function (see :meth:`Timer.timeit`)
      that executes a snippet of "setup" code once and then times some
      number of executions of "stmt" code . The code snippets, given as
      arguments *setup* and *stmt* when creating the instance, may be
      either strings or callable objects.

      If a string, it may contain a python expression, statement, or
      multiple statements separated by ";" or newlines. Whitespace
      adhering to the usual Python indentation rules must follow any
      newlines.

      If a callable object, (often a function), the object is called
      with no arguments. Note that the timing overhead is a little
      larger in this case because of the extra function calls required.

      The setup and stmt parameters default to 'pass'.
      The timer parameter defaults to a platform-dependent
      timer function (see the module doc string).

      When the setup and stmt are run, they are run in a
      different namespace than that of the code that calls timeit().
      To give stmt (whether it is a callable name or code string)
      access to objects defined in the code that calls timeit,
      setup can import any needed objects. For example, if your
      code defines function testfunc(), setup can contain,
      from __main__ import testfunc, and code in stmt can
      then call testfunc.

      To measure the execution time of *stmt*, use the :meth:`Timer.timeit()` method.
      The :meth:`Timer.repeat()` method is a convenience to call :meth:`Timer.timeit()`
      multiple times and return a list of results.

      Changed in version 2.6: The stmt and setup parameters can now
      also take objects.

      Notes:
      ----
      Added the line "Whitespace adhering..." because when using backslash-n
      in strings it is easy to forget about any needed indentation. Sentence
      could be deleted if deemed too obvious. There may also be a better
      way to phrase it; currently it might imply that some whitespace
      is always neccessary if not enough attention paid to the "usual
      indentation rules" phrase.

      ----
      In msg116330 - Eli Bendersky (eli.bendersky) wrote:

      1. My tests show that passing the callable instead of the string
        'test()' actually takes longer to run.

      Should the documentation promise that?
      I take your word that it also takes longer than running the function's
      code directly (outside a function)

      The original "Changed in version 2.6" section said

      | Note that the timing overhead is a little larger in this case
      | [callable objects] because of the extra function calls.

      Here, "the other case" is presumably the plain code but could
      also be a string function call (e.g. "test()") so I suppose it
      is still vacuously true in that case. Accordingly I reused the
      statement above in in my suggested changes. Perhaps all three
      cases (string code, string function call, callable object)
      should be distinguished further and compared re overhead?

    9. elibendersky commented on Jul 24, 2011

      elibenderskymannequin
      Mannequin

      Terry, I'd like to move this forward. New interfaces or not, making the documentation more comprehensible is an important goal in itself.

      Could you please comment on rurpy2's latest notes - I will adapt the patch for latest 2.7/3.2/3.3 heads and commit it.

    10. changed the title [-]timeit execution enviroment[/-] [+][doc] timeit execution enviroment[/+] on Nov 8, 2020
    11. transferred this issue fromon Apr 10, 2022
    12. vasiliyk commented on Oct 23, 2026

      @vasiliyk
      Contributor

      The Timer doc since 3.5 says
      "The statement will by default be executed within timeit’s namespace; this behavior can be controlled by passing a namespace to globals."

      I propose adding one sentence to the Timer class description, right after that one, explaining the consequence:

      Names defined in the calling module are therefore not visible to *stmt* unless *setup* imports them
      or the caller's namespace is passed as *globals*.
      See :ref:`timeit-examples`.

      Examples show both options: setup import and globals usage.
      Happy to open a PR for this.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      docsDocumentation in the Doc dirtype-bugAn unexpected behavior, bug, or error

      Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions