Skip to content

Commit 2d23ca4

Browse files
jgarzikclaude
andcommitted
Python 3.12 compliance: crash fixes, language features, exception types, stdlib modules
Phase 0 — crash/correctness fixes: - Fix raise segfault on non-exception values (strings, ints) - Fix callable() false positives for dict/set/list instances - Fix dict_richcompare r11/r9 clobber bug (5+ entry equality) - Fix UNPACK_SEQUENCE: raise ValueError on count mismatch Phase 1 — core language gaps: - String unpacking: a, b, c = "xyz" - __ne__ auto-derivation from __eq__ for user classes - dict() kwargs and iterable support: dict(x=1), dict([(k,v)]) - Set <=, >=, <, > operators (subset/superset) Phase 2 — 31 new exception types: GeneratorExit, ModuleNotFoundError, SyntaxError, EOFError, UnicodeDecodeError/EncodeError, ConnectionError family, PermissionError, IsADirectoryError, FloatingPointError, BufferError, SystemError, warning subtypes, and more Phase 3 — Ellipsis singleton + breakpoint() stub Phase 4 — pure Python stdlib modules: abc, operator, string, io (StringIO/BytesIO), contextlib, copy (copy/deepcopy), collections (Counter, defaultdict, ChainMap, namedtuple, OrderedDict) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 593933c commit 2d23ca4

17 files changed

Lines changed: 1709 additions & 32 deletions

lib/abc.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# abc.py - Abstract Base Classes (minimal stub for apython)
2+
3+
def abstractmethod(funcobj):
4+
"""Decorator indicating abstract methods."""
5+
funcobj.__isabstractmethod__ = True
6+
return funcobj
7+
8+
class ABCMeta(type):
9+
"""Metaclass for defining Abstract Base Classes (ABCs)."""
10+
pass
11+
12+
class ABC(metaclass=ABCMeta):
13+
"""Helper class that provides a standard way to create an ABC using inheritance."""
14+
__slots__ = ()

lib/collections/__init__.py

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# collections - High-performance container datatypes (minimal for apython)
2+
3+
# OrderedDict is just dict in modern Python (dict preserves insertion order)
4+
OrderedDict = dict
5+
6+
7+
class defaultdict:
8+
"""Dict-like that calls a factory function for missing keys."""
9+
10+
def __init__(self, default_factory=None, *args, **kwargs):
11+
self._data = dict(*args, **kwargs)
12+
self.default_factory = default_factory
13+
14+
def __getitem__(self, key):
15+
try:
16+
return self._data[key]
17+
except KeyError:
18+
if self.default_factory is None:
19+
raise
20+
value = self.default_factory()
21+
self._data[key] = value
22+
return value
23+
24+
def __setitem__(self, key, value):
25+
self._data[key] = value
26+
27+
def __delitem__(self, key):
28+
del self._data[key]
29+
30+
def __contains__(self, key):
31+
return key in self._data
32+
33+
def __len__(self):
34+
return len(self._data)
35+
36+
def __iter__(self):
37+
return iter(self._data)
38+
39+
def __repr__(self):
40+
return "defaultdict(%r, %r)" % (self.default_factory, self._data)
41+
42+
def get(self, key, default=None):
43+
return self._data.get(key, default)
44+
45+
def keys(self):
46+
return self._data.keys()
47+
48+
def values(self):
49+
return self._data.values()
50+
51+
def items(self):
52+
return self._data.items()
53+
54+
def pop(self, key, *args):
55+
return self._data.pop(key, *args)
56+
57+
def update(self, *args, **kwargs):
58+
self._data.update(*args, **kwargs)
59+
60+
61+
class Counter:
62+
"""Dict-like for counting hashable items."""
63+
64+
def __init__(self, iterable=None, **kwargs):
65+
self._data = {}
66+
if iterable is not None:
67+
if isinstance(iterable, dict):
68+
for key, count in iterable.items():
69+
self._data[key] = count
70+
else:
71+
for elem in iterable:
72+
self._data[elem] = self._data.get(elem, 0) + 1
73+
if kwargs:
74+
for key, count in kwargs.items():
75+
self._data[key] = count
76+
77+
def __getitem__(self, key):
78+
return self._data.get(key, 0)
79+
80+
def __setitem__(self, key, value):
81+
self._data[key] = value
82+
83+
def __delitem__(self, key):
84+
del self._data[key]
85+
86+
def __contains__(self, key):
87+
return key in self._data
88+
89+
def __len__(self):
90+
return len(self._data)
91+
92+
def __iter__(self):
93+
return iter(self._data)
94+
95+
def __repr__(self):
96+
return "Counter(%r)" % self._data
97+
98+
def get(self, key, default=None):
99+
return self._data.get(key, default)
100+
101+
def keys(self):
102+
return self._data.keys()
103+
104+
def values(self):
105+
return self._data.values()
106+
107+
def items(self):
108+
return self._data.items()
109+
110+
def most_common(self, n=None):
111+
items = sorted(self._data.items(), key=lambda x: x[1], reverse=True)
112+
if n is not None:
113+
return items[:n]
114+
return items
115+
116+
def elements(self):
117+
for elem, count in self._data.items():
118+
for _ in range(count):
119+
yield elem
120+
121+
def update(self, iterable=None, **kwargs):
122+
if iterable is not None:
123+
if isinstance(iterable, dict):
124+
for key, count in iterable.items():
125+
self._data[key] = self._data.get(key, 0) + count
126+
else:
127+
for elem in iterable:
128+
self._data[elem] = self._data.get(elem, 0) + 1
129+
for key, count in kwargs.items():
130+
self._data[key] = self._data.get(key, 0) + count
131+
132+
def subtract(self, iterable=None, **kwargs):
133+
if iterable is not None:
134+
if isinstance(iterable, dict):
135+
for key, count in iterable.items():
136+
self._data[key] = self._data.get(key, 0) - count
137+
else:
138+
for elem in iterable:
139+
self._data[elem] = self._data.get(elem, 0) - 1
140+
for key, count in kwargs.items():
141+
self._data[key] = self._data.get(key, 0) - count
142+
143+
def total(self):
144+
return sum(self._data.values())
145+
146+
147+
class ChainMap:
148+
"""A ChainMap groups multiple dicts to create a single, updateable view."""
149+
150+
def __init__(self, *maps):
151+
self.maps = list(maps) or [{}]
152+
153+
def __getitem__(self, key):
154+
for mapping in self.maps:
155+
try:
156+
return mapping[key]
157+
except KeyError:
158+
pass
159+
raise KeyError(key)
160+
161+
def __setitem__(self, key, value):
162+
self.maps[0][key] = value
163+
164+
def __delitem__(self, key):
165+
try:
166+
del self.maps[0][key]
167+
except KeyError:
168+
raise KeyError(key)
169+
170+
def __contains__(self, key):
171+
for mapping in self.maps:
172+
if key in mapping:
173+
return True
174+
return False
175+
176+
def __len__(self):
177+
seen = set()
178+
for mapping in self.maps:
179+
for key in mapping:
180+
seen.add(key)
181+
return len(seen)
182+
183+
def get(self, key, default=None):
184+
try:
185+
return self[key]
186+
except KeyError:
187+
return default
188+
189+
def keys(self):
190+
seen = set()
191+
for mapping in self.maps:
192+
for key in mapping:
193+
seen.add(key)
194+
return seen
195+
196+
def values(self):
197+
return [self[key] for key in self.keys()]
198+
199+
def items(self):
200+
return [(key, self[key]) for key in self.keys()]
201+
202+
def new_child(self, m=None):
203+
if m is None:
204+
m = {}
205+
return ChainMap(m, *self.maps)
206+
207+
@property
208+
def parents(self):
209+
return ChainMap(*self.maps[1:])
210+
211+
212+
def namedtuple(typename, field_names, rename=False, defaults=None, module=None):
213+
"""Returns a new subclass of tuple with named fields."""
214+
if isinstance(field_names, str):
215+
field_names = field_names.replace(',', ' ').split()
216+
field_names = tuple(field_names)
217+
num_fields = len(field_names)
218+
219+
# Build the class
220+
class _NT(tuple):
221+
__slots__ = ()
222+
_fields = field_names
223+
224+
def __new__(cls, *args, **kwargs):
225+
if len(args) + len(kwargs) > num_fields:
226+
raise TypeError("Expected %d arguments, got %d" % (num_fields, len(args) + len(kwargs)))
227+
values = list(args)
228+
for name in field_names[len(args):]:
229+
if name in kwargs:
230+
values.append(kwargs[name])
231+
elif defaults is not None and name in field_names[num_fields - len(defaults):]:
232+
idx = list(field_names[num_fields - len(defaults):]).index(name)
233+
values.append(defaults[idx])
234+
else:
235+
raise TypeError("Missing required argument: %r" % name)
236+
return tuple.__new__(cls, values)
237+
238+
def __repr__(self):
239+
parts = []
240+
for i, name in enumerate(field_names):
241+
parts.append("%s=%r" % (name, self[i]))
242+
return "%s(%s)" % (typename, ", ".join(parts))
243+
244+
def _asdict(self):
245+
return dict(zip(field_names, self))
246+
247+
def _replace(self, **kwargs):
248+
d = self._asdict()
249+
d.update(kwargs)
250+
return type(self)(**d)
251+
252+
_NT.__name__ = typename
253+
_NT.__qualname__ = typename
254+
255+
# Add property accessors for each field
256+
for i, name in enumerate(field_names):
257+
def _getter(self, i=i):
258+
return self[i]
259+
setattr(_NT, name, property(_getter))
260+
261+
return _NT

lib/contextlib.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# contextlib.py - Utilities for with-statement contexts (minimal for apython)
2+
3+
4+
class contextmanager:
5+
"""Decorator to turn a generator function into a context manager."""
6+
7+
def __init__(self, func):
8+
self._func = func
9+
10+
def __call__(self, *args, **kwargs):
11+
return _GeneratorContextManager(self._func, args, kwargs)
12+
13+
14+
class _GeneratorContextManager:
15+
"""Helper for @contextmanager decorator."""
16+
17+
def __init__(self, func, args, kwargs):
18+
self._gen = func(*args, **kwargs)
19+
20+
def __enter__(self):
21+
try:
22+
return next(self._gen)
23+
except StopIteration:
24+
raise RuntimeError("generator didn't yield")
25+
26+
def __exit__(self, typ, value, traceback):
27+
if typ is None:
28+
try:
29+
next(self._gen)
30+
except StopIteration:
31+
return False
32+
raise RuntimeError("generator didn't stop")
33+
else:
34+
if value is None:
35+
value = typ()
36+
try:
37+
next(self._gen)
38+
except StopIteration:
39+
return False
40+
except BaseException as exc:
41+
if exc is not value:
42+
raise
43+
return False
44+
raise RuntimeError("generator didn't stop after throw")
45+
46+
47+
class suppress:
48+
"""Context manager to suppress specified exceptions."""
49+
50+
def __init__(self, *exceptions):
51+
self._exceptions = exceptions
52+
53+
def __enter__(self):
54+
return self
55+
56+
def __exit__(self, exctype, excinst, exctb):
57+
return exctype is not None and issubclass(exctype, self._exceptions)
58+
59+
60+
class closing:
61+
"""Context manager for objects with a close() method."""
62+
63+
def __init__(self, thing):
64+
self.thing = thing
65+
66+
def __enter__(self):
67+
return self.thing
68+
69+
def __exit__(self, *exc_info):
70+
self.thing.close()
71+
72+
73+
class redirect_stdout:
74+
"""Context manager for temporarily redirecting stdout."""
75+
76+
def __init__(self, new_target):
77+
self._new_target = new_target
78+
79+
def __enter__(self):
80+
import sys
81+
self._old_target = sys.stdout
82+
sys.stdout = self._new_target
83+
return self._new_target
84+
85+
def __exit__(self, exctype, excinst, exctb):
86+
import sys
87+
sys.stdout = self._old_target
88+
89+
90+
class redirect_stderr:
91+
"""Context manager for temporarily redirecting stderr."""
92+
93+
def __init__(self, new_target):
94+
self._new_target = new_target
95+
96+
def __enter__(self):
97+
import sys
98+
self._old_target = sys.stderr
99+
sys.stderr = self._new_target
100+
return self._new_target
101+
102+
def __exit__(self, exctype, excinst, exctb):
103+
import sys
104+
sys.stderr = self._old_target
105+
106+
107+
class nullcontext:
108+
"""Context manager that does nothing."""
109+
110+
def __init__(self, enter_result=None):
111+
self.enter_result = enter_result
112+
113+
def __enter__(self):
114+
return self.enter_result
115+
116+
def __exit__(self, *excinfo):
117+
pass

0 commit comments

Comments
 (0)