|
51 | 51 | DEFAULT_ENCODING = 'UTF-8' |
52 | 52 |
|
53 | 53 |
|
| 54 | +def _reject_bare_str_or_bytes(value: object, expected: str) -> None: |
| 55 | + # A bare string is an iterable of its characters, so e.g. set('dr') or |
| 56 | + # dict('ab') would silently shred it, and bytes iterates to ints, which |
| 57 | + # can never match parsed str tokens -- shared by SetManager's constructor/ |
| 58 | + # operands (#238) and TupleManager's constructor (#242). |
| 59 | + if isinstance(value, bytes): |
| 60 | + raise TypeError( |
| 61 | + f"expected {expected}, got a single bytes; " |
| 62 | + f"decode it first: [{value!r}.decode()]" |
| 63 | + ) |
| 64 | + if isinstance(value, str): |
| 65 | + raise TypeError( |
| 66 | + f"expected {expected}, got a single str; wrap it in a list: [{value!r}]" |
| 67 | + ) |
| 68 | + |
| 69 | + |
54 | 70 | class SetManager(Set): |
55 | 71 | ''' |
56 | 72 | Easily add and remove config variables per module or instance. Subclass of |
@@ -78,19 +94,7 @@ def _normalized_elements(cls, elements: Iterable[str]) -> set[str]: |
78 | 94 | # construction from re-checking ~1,400 entries per step |
79 | 95 | if isinstance(elements, SetManager): |
80 | 96 | return set(elements.elements) |
81 | | - # a bare string is an iterable of its characters, so set(value) |
82 | | - # would silently build a set of single characters — and bytes |
83 | | - # iterates to ints, which can never match parsed tokens (#238) |
84 | | - if isinstance(elements, bytes): |
85 | | - raise TypeError( |
86 | | - "expected an iterable of strings, got a single bytes; " |
87 | | - f"decode it first: [{elements!r}.decode()]" |
88 | | - ) |
89 | | - if isinstance(elements, str): |
90 | | - raise TypeError( |
91 | | - "expected an iterable of strings, got a single str; " |
92 | | - f"wrap it in a list: [{elements!r}]" |
93 | | - ) |
| 97 | + _reject_bare_str_or_bytes(elements, "an iterable of strings") |
94 | 98 | # apply the same lc() normalization (lowercase, strip leading/ |
95 | 99 | # trailing periods) that add() applies, and reject junk elements: |
96 | 100 | # lc() on bytes or int crashes without naming the culprit, and |
@@ -148,12 +152,13 @@ def __contains__(self, value: object) -> bool: |
148 | 152 | # add()/remove()/the constructor/the operators all normalize (lowercase, |
149 | 153 | # strip leading/trailing periods) before comparing; without the same |
150 | 154 | # normalization here, `'Dr.' in c.titles` returns False even though |
151 | | - # every other operation on the same value succeeds (#244). lc() is |
152 | | - # idempotent, so this doesn't change the parser's own lc()-normalized |
153 | | - # lookups (e.g. `piece.lower() in self.C.conjunctions`). |
154 | | - if isinstance(value, str): |
155 | | - value = lc(value) |
156 | | - return value in self.elements |
| 155 | + # every other operation on the same value succeeds (#244). The parser's |
| 156 | + # own lookups (e.g. `piece.lower() in self.C.conjunctions`) already pass |
| 157 | + # an lc()-normalized value, which is the hot path during parsing, so |
| 158 | + # try the raw value first and only pay for lc() on a miss. |
| 159 | + if value in self.elements: |
| 160 | + return True |
| 161 | + return isinstance(value, str) and lc(value) in self.elements |
157 | 162 |
|
158 | 163 | def __len__(self) -> int: |
159 | 164 | return len(self.elements) |
@@ -296,16 +301,7 @@ def __init__( |
296 | 301 | # pair, silently shredding it -- mirrors SetManager's guard against |
297 | 302 | # the same class of mistake (#238), applied to the mapping |
298 | 303 | # constructor's own failure modes (#242). |
299 | | - if isinstance(arg, bytes): |
300 | | - raise TypeError( |
301 | | - "expected a mapping or iterable of (key, value) pairs, got a " |
302 | | - f"single bytes; decode it first: [{arg!r}.decode()]" |
303 | | - ) |
304 | | - if isinstance(arg, str): |
305 | | - raise TypeError( |
306 | | - "expected a mapping or iterable of (key, value) pairs, got a " |
307 | | - f"single str; wrap it in a list: [{arg!r}]" |
308 | | - ) |
| 304 | + _reject_bare_str_or_bytes(arg, "a mapping or iterable of (key, value) pairs") |
309 | 305 | if not isinstance(arg, Mapping): |
310 | 306 | checked = [] |
311 | 307 | for item in arg: |
|
0 commit comments