-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
gh-117348: restore import time performance of configparser #117703
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
Conversation
Analysis: baseline prior to #117372
degradation from #117372
restoration with this PR
|
Reduces import time by over 50% (10431µs vs 4350µs on Apple M3 Pro).
039ff2d
to
11bae33
Compare
Nice, thank you!
I don't oppose this necessarily, but I'm curious what inheriting from --- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -154,7 +154,6 @@
import os
import re
import sys
-import types
__all__ = ("NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
"NoOptionError", "InterpolationError", "InterpolationDepthError",
@@ -539,7 +538,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map,
"found: %r" % (rest,))
-class _ReadState(types.SimpleNamespace):
+class _ReadState:
elements_added : set[str]
cursect : dict[str, str] | None = None
sectname : str | None = None
@@ -553,9 +552,10 @@ def __init__(self):
self.errors = list()
-class _Prefixes(types.SimpleNamespace):
- full : Iterable[str]
- inline : Iterable[str]
+class _Prefixes:
+ def __init__(self, full: Iterable[str], inline: Iterable[str]):
+ self.full = full
+ self.inline = inline This also feels like it reduces conceptual complexity for me -- it's just a normal Python class; I don't have to go and look up what exactly the behaviour of |
My intention is to re-use higher level abstractions that reduce the amount of boilerplate (number of times a member variable must be mentioned to be functional) and imperative code. It provides ~half of what dataclasses provides (namely, a default constructor with useful semantics and constraints). You're right - using In the case of class _Prefixes:
def __init__(self, full: Iterable[str], inline: Iterable[str]):
self.full = full
self.inline = inline + ('hot garbage',)
self.other_state = random.random() By re-using the constructor from SimpleNamespace, a reader can know at a glance the behavior of the constructor and static analysis tools can make assumptions that it can't about an explicit constructor. I think it's worth using here for that value alone, but it also adds value in utilizing this pattern that's previously been relegated to third-party packages like munch and thus making it less likely that a reader will find it surprising. Given that we're dropping dataclasses, however, it may not even be necessary to declare a type for this container at all and instead just construct a diff --git a/Lib/configparser.py b/Lib/configparser.py
index b2b85b7af2..f6438699c9 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -553,17 +553,12 @@ def __init__(self):
self.errors = list()
-class _Prefixes(types.SimpleNamespace):
- full : Iterable[str]
- inline : Iterable[str]
-
-
class _Line(str):
def __new__(cls, val, *args, **kwargs):
return super().__new__(cls, val)
- def __init__(self, val, prefixes: _Prefixes):
+ def __init__(self, val, prefixes):
self.prefixes = prefixes
@functools.cached_property
@@ -656,7 +651,7 @@ def __init__(self, defaults=None, dict_type=_default_dict,
else:
self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
re.VERBOSE)
- self._prefixes = _Prefixes(
+ self._prefixes = types.SimpleNamespace(
full=tuple(comment_prefixes or ()),
inline=tuple(inline_comment_prefixes or ()),
) I think I prefer the presence of And until I can have something like dataclasses, I'd like to use |
Note that tools that provide code completion generally ignore all type hints in the stdlib, and instead rely entirely on the type annotations in typeshed for this purpose |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would still lean towards keeping _Prefixes
dead simple, and defining it without SimpleNamespace
(or using an inline SimpleNamespace
as you suggested enough). If you want to enforce that keyword arguments are passed to the constructor, that's pretty easily accomplished without using SimpleNamespace
:
class _Prefixes:
def __init__(self, *, full: Iterable[str], inline: Iterable[str]):
self.full = full
self.inline = inline
But that's just my minor personal preference. What you have is fine. Thanks again for fixing!
I've opted to use the SimpleNamespace inline. Thanks for the review and thoughtful suggestions. |
…hon#117703) Reduces import time by over 50% (10431µs vs 4350µs on Apple M3 Pro).
As requested in #117372 (comment)