Skip to content

gh-137578: support top-level setup statements in Timer objects #137587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

picnixz
Copy link
Member

@picnixz picnixz commented Aug 9, 2025

@picnixz picnixz force-pushed the feat/timeit/global-setup-137578 branch from 26c22fd to a63c5f1 Compare August 9, 2025 12:30
@picnixz picnixz marked this pull request as ready for review August 9, 2025 12:32
@picnixz picnixz requested a review from AA-Turner as a code owner August 9, 2025 12:32
Comment on lines +123 to +125
*stmt*, *setup* and *global_setup* may also contain multiple statements
separated by ``;`` or newlines, as long as they don't contain multi-line
string literals.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this has been in the docs for a while, but why aren't multi-line strings allowed? A quick test shows the implementation allows them, though I suppose strings inside stmt or setup may get indented differently than the user intends.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we reindent the user's code by replacing \n (it's a naive reindentation, not based on textwrap and possibly because of historical reasons). Maybe we should instead use dedent() followed by indent(), in which case that could be ok I guess. However, this should be a separate issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, at least for the new global_setup that doesn't apply. But even for the existing parameters I'm not sure multi-line strings are prohibited as the docs suggest or just may produce unexpected results (due to reindenting). Regardless we can open a separate issue to discuss a docs change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, let's discuss it separately.

- carry co_flags obtained from global setup
- update timeit() and repeat() functions
Copy link
Member Author

@picnixz picnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I want to change the way we create the main() function because I think it'd be cleaner if we used argparse instead.

if isinstance(global_setup, str):
# Check that the code can be compiled outside a function.
code = compile(global_setup, dummy_src_name, "exec")
exec(global_setup, global_ns, local_setup_ns)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why execute it separately?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you talking about the code variable? or the local namespace?

This comment was marked as resolved.

Copy link
Member Author

@picnixz picnixz Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it would have been fine to use the prefix approach as for the others, but the problem would be with variables declared at the global setup phase that aren't visible inside the inner function: ./python -m timeit -n1 -g 's=1' 'print(s)' (they are not visible because we use a specific global dict and a specific local dict as well)

s=1

def inner(_it, _timer):
    pass
    _t0 = _timer()
    for _i in _it:
        print(s)  # NameError
        pass
    _t1 = _timer()
    return _t1 - _t0

Therefore, I need to pick all variables that are declared in the global scope and make them available in the function (but global_ns doesn't have those variables yet). Or am I wrong here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inner function will capture variables created by the global setup if use the same dict for globals and locals.

        exec(code, global_ns, global_ns)
        self.inner = global_ns["inner"]

Of course, inner should be renamed to avoid possible conflicts.

I don't know what approach is better. I only say that there is other approach and we should consider it it see what is better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've considered renaming all the internal variables in the template with a suffix os.urandom(8).hex() but felt that it shouldn't have been part of this PR.

Copy link
Member

@serhiy-storchaka serhiy-storchaka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All LGTM, but we can try other approach before choosing the best.

It may be worth to mention that names created in the global setup will be seen as globals in the setup and testing code.

if isinstance(global_setup, str):
# Check that the code can be compiled outside a function.
code = compile(global_setup, dummy_src_name, "exec")
exec(global_setup, global_ns, local_setup_ns)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inner function will capture variables created by the global setup if use the same dict for globals and locals.

        exec(code, global_ns, global_ns)
        self.inner = global_ns["inner"]

Of course, inner should be renamed to avoid possible conflicts.

I don't know what approach is better. I only say that there is other approach and we should consider it it see what is better.

@picnixz
Copy link
Member Author

picnixz commented Aug 22, 2025

It may be worth to mention that names created in the global setup will be seen as globals in the setup and testing code.

That would indeed be a good idea.

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

Successfully merging this pull request may close these issues.

3 participants