#internals-and-peps
1 messages · Page 35 of 1
the same literal instance? This one doesn't surprise me
I expected them to be the same instance. their state is tied to the dict that they came from, so there's nothing that can be instance-specific.
Hm, yeah, but like what if you did the iter(d.keys()), wouldn't the iteration state be held on that KeysView instance?
if it always returned the same instance, that wouldn't work if u wanted to have multiple iteration states going on
i dont know why you would, but it would be a limitation
no, you'd get a dict_keyiterator object.
most iterable builtins return a separate iterator object for iteration
!e ```py
d = {"a": 1, "b": 2}
k = d.keys()
iterk = iter(k)
print(next(iterk))
del d["b"]
d["c"] = 3
print(next(iterk))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | a
002 | c
I was today years old when I found out the Python object instance parameter on instance functions can be named anything and that self is not actually a keyword
This is near the top of my list of Python trivia that experts say to beginners that isn't helpful. They're trying to learn about OOP, and after someone explains what self is, an expert will say, "And btw, you can name it anything."
Definitely seems like a fact that can be handwaved over until the individual learns about method binding, if at all
Wow, thanks for this
ew... it only checks that the size didn't change during iteration
we need dict versions…
every time the content changes, increment an atomic integer…
It only checks that because that can cause the underlying hash table to be resized. Other mutations don't pose the same problems for iterators
!e ```py
from sys import*
d = {1:1,2:2,3:3}
k = d.keys()
iterk = iter(k)
print(getsizeof(d))
print(next(iterk))
for i in range(10):
d[i] = i
for i in range(3, 10):
del d[i]
print(getsizeof(d))
print(next(iterk))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | 224
002 | 1
003 | 352
004 | 2
Here I change the size of the hash table, but it still doesn't notice, because the len is identical at the end..
Maybe it mattered for the previous version of dict (before the change that made it retain insertion order), and this checking was preserved in case people relied on it?
That does seem nasty
@staticmethod
def _lazy_compile(func):
"""truncated"""
real_func = func.__argmap__.compile(func.__wrapped__)
func.__code__ = real_func.__code__
func.__globals__.update(real_func.__globals__)
func.__dict__.update(real_func.__dict__)
return func
I didn't think any of those dunders on __func__ were writeable
the last two outputs surprise me
In [7]: n = 'hi'
In [8]: def my_func(x):
...: m = 'bye'
...: result = n + m + x
...: return result
...:
In [9]: my_func('bar')
Out[9]: 'hibyebar'
In [10]: my_func.__globals__['n'] = 'HI!'
In [11]: n
Out[11]: 'HI!'
In [12]: my_func('bar')
Out[12]: 'HI!byebar'
Yeah, you can do some nasty stuff with it ```txt
def f():
... return 420
...
f.code.co_consts
(420,)
f.code = f.code.replace(co_consts=(69,))
f()
69
f.code = f.code.replace(co_consts=())
f()
Segmentation fault (core dumped)
easiest way to crash python
that might be ctypes.string_at(0)
needs an import, too much work
It's not a crash, but afaik set(iter(int,1)) will trigger an un-interruptible infinite loop
Seeing bytecode of a function being replaced as easily as one would reassign a variable was not on my 2026 bingo card
it will crash when it eventually runs out of memory
oh it won't (because it's a set). hmm
It might seem initially surprising, but makes sense as my_func.__globals__ is the same object as the module globals.
The results would probably be less surprising written as:
>>> n = 'hi'
...
... def my_func(x):
... m = "bye"
... result = n + m + x
... return result
...
>>> my_func('bar')
'hibyebar'
>>> globals()["n"] = "HI!"
>>> n
'HI!'
>>> my_func('bar')
'HI!byebar'
>>> my_func.__globals__ is globals()
True
yeah, that does make sense. Thanks!
This is a very stupid question but is Python's VM instruction set like Java's?
What I mean by that is whether it's stable and you can reliably compile other languages to it/use it as a language backend target like you can with the Java instruction set and run the compiled code on a Python VM
I was just thinking about doing that some time ago
But I just realized that it might be an exercise in futility if the answer to my question is no
ever heard of hy? https://hylang.org/
it's a lisp that compiles to python bytecode
I don't think there's much demand for this because the python bytecode interpreter isn't optimized like the JVM is
No, I haven't up till now, interesting
I feel that's no longer entirely true with faster-cpython
The second half of that statement I mean
Ah right, I guess this also means that what I was thinking about was feasible, nice
there's definitely movement towards improving performance
it's also not the JVM yet 🙂
I don't think we'll get there anytime soon unless a company seriously invests in it
microsoft stopped faster cpython 🙁
May last year they did a big round of layoffs and that included the whole faster cpython team. There's still effort towards that from volunteers and lots of funded performance work still happening, just not from microsoft.
Sometimes I wonder what Microsoft is thinking
Sigh
Hmm
Would be interesting if you could crack open HotSpot's source code and take useful performance stuff from there and put it in cpython (Or maybe V8 if you wanted a more similar dynamic language to Python) but that's probably not going to work, the former is a very complex codebase and the latter is even worse and more unreadable
I also don't know if that's legal or not lmao
the JVM can also reorder operations and has a whole synchronization and thread safety story, I doubt Python will ever do that.
well, beyond “it can’t crash the interpreter” and “python code runs sequentially”
Oh the abi3t PEP, nice
Is it the lack of funding or lack of (Perhaps highly experienced) developers that hurts faster-cpython more when we're talking about needing a company to invest in it for it to perform like gcc -O3? (That last part is hyperbole, you get what I mean)
not a lack of funding so much as it being a hard social problem that you can’t throw money or developers at, particularly around managing backward compatibility and technical complexity budgets
also the C API makes a lot of optimizations hard
python exposes a lot of internal details
That makes sense in my head but is also profoundly unfortunate
Can't just break all of that to be faster, I get that
well, it just takes time
there’s movement toward making more of CPython internals opaque (that’s part of PEP 803 which I referred to earlier)
but the C API evolves slowly and projects drop support for old python versions on their own timescales
That's good to hear
the JVM also has the advantage of java code being largely pure-java, which makes optimizing more effective
cpython calls into a lot of C code that requires the interpreter to stop tracing
There are techniques to trace with FFI if I recall correctly, but it's incredibly difficult
there was a soltuion demonstrated at the core sprint in september using a new C API layer that was basically hpy. don't know what happened to it though
The only HPy I know is the thing that PyPy has in their interpreter
What is HPy?
HPy provides a new API for extending Python in C. In other words, you use
#include <hpy.h> instead of #include <Python.h>.
What are the advantages of HPy?
Zero overhead on CPython: ex
Ah
On another note
What does this mean?
ERROR: /src/Include/object.h not found
Did you forget to mount Python work directory with '-v.:/src'?
All I'm doing is running regen-configure.sh (Specified as an absolute path) in the root directory of cpython
I haven't the faintest clue of how podman works
Try running it not as an absolute path.
Also, note that you don’t need to regenerate it (or any of the generated files, we have checks in our CI to ensure they are always up to date) just to build CPython.
I'm modifying configure.ac to test something, gotta regenerate it haha
Thanks though!
If that isn’t the issue, do you have SELinux enabled?
is there a specific reason that we don't have float.inf and float.nan?
I know we have float('inf') et al, but I think having them as attributes looks cleaner
there's math.inf and math.nan
I suppose, if nan was a float attribute, you could accidentally write something like py def f(x: float): if x.nan: ... instead of ```py
def f(x: float):
if math.isnan(x):
...
I'm not sure what SELinux is :P
Wait I got it running, but it errors after a while
+ IMAGE=ghcr.io/python/autoconf:2025.01.02.12581854023
+ AUTORECONF='autoreconf -ivf -Werror'
+ WORK_DIR=/src
+++ dirname /mnt/c/Users/vertig0/Downloads/eclipse-committers-2023-12-R-win32-x86_64/Workspace/python/cpython/Tools/build/regen-configure.sh
++ cd /mnt/c/Users/vertig0/Downloads/eclipse-committers-2023-12-R-win32-x86_64/Workspace/python/cpython/Tools/build/../..
++ pwd
+ abs_srcdir=/mnt/c/Users/vertig0/Downloads/eclipse-committers-2023-12-R-win32-x86_64/Workspace/python/cpython
+ podman --version
+ RUNTIME=podman
+ PATH_OPT=
+ command -v selinuxenabled
+ podman run --rm -v /mnt/c/Users/vertig0/Downloads/eclipse-committers-2023-12-R-win32-x86_64/Workspace/python/cpython:/src ghcr.io/python/autoconf:2025.01.02.12581854023
Rebuilding configure script using autoconf (GNU Autoconf) 2.72
autoreconf: export WARNINGS=error
autoreconf: Entering directory '.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force
' is already registered with AC_CONFIG_FILES.
./lib/autoconf/status.m4:289: AC_CONFIG_FILES is expanded from...
configure.ac:8337: the top level
autom4te: error: /usr/bin/m4 failed with exit status: 1
aclocal: error: /usr/local/bin/autom4te failed with exit status: 1
autoreconf: error: aclocal failed with exit status: 1
Ok I am so confused it worked after I merged with latest main
My best guess is line ending issues that somehow got fixed
Pep827 looks thick
!pep 827
It has so many things I want badly
Only thing I don't like is the callable stuff because I would want actual syntax for it
It will be really cool if it's accepted
I don't know enough to have commentary. I don't really understand a lot of it
i like how this completely ignores the fact that args and kwargs are not mandatory names
well, actually...
I think a good lesson to learn from TypeScript is that, while the operators it has are very cool, they create a completely new sub-language that's very different from the base language
If Python wants a more powerful typing system, maybe a better direction would be something like what pyanalyze had. Defining small Python functions with normal Python syntax (with a restricted set of features) that implement complex type manipulations
but I'm not in charge of any of it, so I guess we'll see what happens
So instead of this: ```py
Generate the Member field for init for a class
type InitFnType[T] = typing.Member[
Literal["init"],
Callable[
[
typing.Param[Literal["self"], Self],
*[
typing.Param[
p.name,
p.type,
Literal["keyword"]
if typing.IsAssignable[
GetDefault[p.init],
Never,
]
else Literal["keyword", "default"],
]
for p in typing.Iter[typing.Attrs[T]]
],
],
None,
],
Literal["ClassVar"],
]
we could have this code that's definitely Pythonpy
@type_function
def init_fn_type(t: type[object]):
params = [Param("self", Self, pos_only=True)]
for attr in iter_attrs(t):
param = Param(attr.name, attr.type, kw_only=True)
if attr.has_default():
param.default = attr.default # setting a default instead of just marking as "not required" (so it can be displayed when you hover over the callable or reveal_type it)
params.append(param)
return Member(name="init", kind="classvar", type=Callable[[*params], None]) # keyword arguments are great
i think the grammar path is already there, but i don't know where it's used
wdym I didn't see anything for it, the last I've heard on this was PEP 677
the first example seems ridiculously verbose. I wonder if the pep will get rejected due to that. Maybe that's the point, get it rejected and hope for a more concise syntax response and make a new one
Yeah, I was kind of surprised this snippet made it into the PEP 😄
that looks like a joke someone would make about how complicated python's type annotations became
I still don't get why we have to wrap Literal["String"] and it cant just infer "String" as literal
instances of int/bool/float/str should be special cased
yeah | is certainly an issue for that isnt it
Guess that ship sailed when they allowed | for union
that's a big shame
There's really no way in the grammar to give context to the operators?
Give someone different meaning if it comes after type : or -> ?
runtime... cant do anything about runtime, didn't read Jelle's post close enough
An example of a type-only thing that cannot be special cased is pre-3.12 type aliases: Foo: TypeAlias = Literal[420, 69], though they are deprecated with no 1-to-1 replacement
didn't someone suggest set literals as an alternative?
Literal[420, 69] -> {420, 69}
though I like Literal as-is.
!cleanban @cursive bobcat free macbook scam
:incoming_envelope: :ok_hand: applied ban to @cursive bobcat permanently.
enum Literals are not necessarily hashable
I like the ability to actually declare what kind of function with what kind of signature you're getting. However I think it also needs to come down on some side of how string annotations are going to still work at runtime.
!e it ignores star unpacks and it's AST-only so it's not really useful (as i said i don't know where it's used), but it's similar to PEP 677 without the async part ```py
import ast
print(compile('(int, str, *x) -> str', 'a', 'func_type', flags=ast.PyCF_ONLY_AST))
https://github.com/python/cpython/blob/main/Grammar/python.gram#L92
:white_check_mark: Your 3.14 eval job has completed with return code 0.
FunctionType(argtypes=[Name(id='int', ctx=Load()), ..., Name(id='x', ctx=Load())], returns=Name(id='str', ctx=Load()))
Grammar/python.gram line 92
func_type[mod_ty]: '(' a=[type_expressions] ')' '->' b=expression NEWLINE* ENDMARKER { _PyAST_FunctionType(a, b, p->arena) }```
Speaking of typing are we going to get Python where the types are used instead of just being there for code editors to analyze
There already are tools that use them at runtime
TIL
Very cool
I do fear what's going to happen long term, maybe its overblown, it just seems like typing is becoming its own mini language and a verbose one at that. Like where does it stop? Full blown integration in a non backwards compatible python 4.0?
highly doubt python will abandon being dynamically typed
but the developers' choices are a different story
nice
I don't think we will ever see a Python 4.
You think annotations will ever get better first class support? Or forever be a mini langauge?
i'm not sure what better support you are imagining. They are how they are because they have to fit into being python expressions.
it's for parsing old-style # type: comments. from the docs:
def sum_two_number(a, b):
# type: (int, int) -> int
return a + b
As far as I'm aware Python itself ignores them entirely
There are things in the stdlib that make use of annotations for specific features, such as dataclasses with KW_ONLY, ClassVar and InitVar.
i mean making python fully statically typed
I think Python is going to keep annotations as a very optional add-on for people who like static analysis forever (until people stop using Python)
me too, but it wouldn't surprise me if the stdlib were to become statically typed someday
I could see type hints in some parts of the stdlib
Enforced static typing would not only mean a complete overhaul of the base language, but also of the type system. Because it's designed around being based on a dynamically typed foundation; and having holes/inconsistencies is tolerated. So it would just be a new language
no like enforcing static typing in python as a whole
There are also libraries like beartype that I believe will do things like this.
no, beartype inserts runtime checks
Static typing means that type consistency is checked before the program is run
Ah, I see the distinction
I don't see that as desirable
At least not with the type system we have
I also think it would make some of the more scripting-style use cases for Python more tedious.
mypyc is an interesting tool that I need to try out
if that ever happens they might as well fix the inconsistency in stdlib naming with it
I don't think that's ever going to happen. None of the major libraries written for Python will keep working. If you just want a fast and statically typed language, there are many alternatives. Python doesn't have to be everything
I was more thinking of the route that PHP went, not making Python a static language
What does PHP do?
Looks like it does automatic coercion unless strict mode is enabled. https://www.php.net/manual/en/language.types.declarations.php#language.types.declarations.strict
PHP inserts type checks when you use type annotations, it's pretty terrible since you obviously can't do things like specify the types of array contents (at least last I checked), so you end up with a separate questionably enforced static type system anyway.
Things can be any type by default, but switches to enforcing types when declared with an explicit type
Strict mode is a different thing entirely
ah, so runtime type checking
I think GDScript has something similar
Raku instead solves this by making creating arrays with typed contents incredibly unergonomic, and if you don't do this, your array can't be assigned to anything
It seems like there's a lot of verbosity with annotations because
1.) runtime applications
2.) choice of operators for annotations that conflict with existing types and/or instances of types
So what I'm asking for is something that makes typing them out, especially the crazy ones in the pep, in a more concise manner
i don't think you are going to get away from annotations needing to be valid Python expressions.
A lot of the less nice parts of annotations have already been improved
Unions, generics
I think in the latest python it's already pretty good
hello
TIL KeyError works this way:
first line
second line
>>> print(str(KeyError("first line\nsecond line")))
'first line\nsecond line'```
do you think that's specified?
Guido closed the issue I opened about it 🤣
you got guido'ed! 
what did he say?
It's intentional. ValueError's argument is an error message. KeyError's argument is the missing key, which must be rendered using repr() to clarify its type.
he has spoken
the wisdom of the dutch
it is now officially a rule on this server that any time guido is quoted, there must be
on both sides
One of them should be upside-down
¡!
that way must've seemed obvious to him
well yep he's right
because this happens you can get KeyError: 'key name' in tracebacks
this is funny if you actually want a key error with some more details, but i guess you can just use .add_note since 3.11
you can subclass KeyError and define __repr__ too
Hey all, quick question - would there be interest in adding safer defaults to the standard library's zipfile.extractall() function? Right now there's no protection against zip bombs when extracting untrusted zips, which could be a real issue when people ask LLMs to generate code. Just curious if this is something worth pushing forward on.
There was an issue for it that got closed, so probably no? https://github.com/python/cpython/issues/80643
Good catch! This issue is 6 years old now, and I think the situation has not improved. The approach in #80643 is to limit the compression ratio, but there are some cases where it could legitimately be high. I was considering something of a parameter that added a max extracted size.
Hello guys ! I want to ask if this server is for learning scripting and coding
I want to learn some
Yep, you can do that, just in other channels (This one is for discussing the internals of the Python runtime)
What do you think about this issue with initializer function in multiprocessing.Pool
https://stackoverflow.com/questions/47586645/how-to-handle-initializer-error-in-multiprocessing-pool
Is it something that could/should be raised as a python issue?
!cpban 1474812346684670094 crypto scam
:incoming_envelope: :ok_hand: applied ban to @stone temple until <t:1773152757:f> (4 days).
I'd open a PR to fix this typo but idk where these docs come from.
Out of all the channels, this seemed maybe the most appropriate to post something like this in. Let me know if I'm wrong
I vote to remove "are" instead of "be" so it sounds like a pirate
Looks like it's already fixed in https://github.com/python/cpython/blob/main/Doc/library/functools.rst
But the procedure would just have been to send a PR changing that file
I wonder if it's safe to assume that VALUE_WITH_FAKE_GLOBALS works for annotate functions if they've been retrieved from __annotate_func__ on a class and not __annotate__. A user function that doesn't support the fake globals evaluation should come from __annotate__ directly.
Context is that Format.STRING currently has to call annotate(Format.VALUE_WITH_FAKE_GLOBALS) in the regular globals in order to check if fake globals are supported and this then evaluates the annotations.
Essentially I'd like to be able to grab some form of annotations without triggering any PEP-810 lazy imports.
Answer: No, __annotate__ acts as a hidden descriptor, if you try to set it it sets __annotate_func__ instead
this ended up coming down to glob ordering being undefined: https://github.com/python/cpython/issues/145607
Bug report test_bz2 concatenates a bunch of Python test files to get 128 KiB of test data: cpython/Lib/test/test_bz2.py Lines 69 to 80 in 1d091a3 # Some tests need more than one block of uncompress...
Oh, it's even weirder - you can set __annotate__ by actually defining it as a class method. But due to the __annotate_func__ behaviour you.. can't replace it?
!e
from annotationlib import get_annotations, get_annotate_from_class_namespace, Format
class Example:
@staticmethod
def __annotate__(format):
print("Declared Method Called")
return {}
get_annotations(Example, format=Format.STRING)
# Try to replace __annotate__
def annotate(format):
print("Replacement Function Called")
return {}
setattr(Example, "__annotate__", annotate)
# Old method still used
get_annotations(Example, format=Format.STRING)
print(get_annotate_from_class_namespace(Example.__dict__))
print(Example.__dict__["__annotate_func__"]) # attempting to set __annotate__ has set this instead
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | Declared Method Called
002 | Declared Method Called
003 | <staticmethod(<function Example.__annotate__ at 0x7f95e269b060>)>
004 | <function annotate at 0x7f95e26433d0>
helloooo guys i have a project that my friend has done its OOP error handling with (try, except, finally and raise) and i just wanted to know would anyone be able to hop on a call and just help me understand some things
ask in #python-discussion
idk if this is the wrong place, but could someone explain why operator.call doesn't move references? (maybe this belongs in #c-extensions?)
import operator
class WarnsWhenDeleted:
def __del__(self):
print("yeah, I was just deleted....")
def without_generator():
def x(t):
del t
print("leaving x")
operator.call(x, WarnsWhenDeleted())
print("leaving overall func")
without_generator()
specifically, if i replace operator.call(...) with x(WarnsWhenDeleted()), i get the __del__ message before "leaving x", and vice versa otherwise.
I don't think that's specific to operator.call. The WarnsWhenDeleted object is an argument to that function, so it gets kept alive while the function is alive.
to call C functions the interpreter needs to construct an argument tuple, which keeps the ref alive
I think it can skip that for pure-python stuff?
As like a perf thing probably? Interesting
yeah. technically, the ref is moved as of 3.14 due to LOAD_FAST_BORROW; it's just released in a difference place.
Why does this need a new reference? (Not too familiar with internals)
the tuple has a reference to the object
but not sure it's actually about the tuple, might be more about the locals of the calling function
operator.call specifically probably does hold a tuple
But shouldn’t the tuple then take (“steal”?) the reference? (Now I at least understand what might be going on though xd, I was v confused)
ah, it uses METH_FASTCALL, so it has a C array rather than a tuple, but it's the same concept
it does, I believe. the behavior you're seeing is because operator.call doesn't move the reference from the tuple into x
basically:
- eval loop creates
WarnsWhenDeletedinstance - finds a C function (
operator.call), moves theWarnsWhenDeletedinstance to an argument tuple (I think? there might be an extra incref/decref) - calls the C function with that tuple
operator.callexecutesxxdeletes its local reference, but the arguments tooperator.callstill hold a referenceoperator.callfinishes and releases the reference, deallocating the object
Hm, is there a reason why it doesn’t move the reference over? Generality to allow operator.call to call some naughty C function?
operator.call doesn't know what it's calling ahead of time
I guess I’ll poke at the code and see if this all makes sense…
hm, I guess it would technically be possible to move it, but it'd require a whole new calling protocol. we'd need a way for operator.call to own its arguments, rather than the method object owning them, and then be able to move that via vectorcall
the code might be pretty opaque here, the refcounting stuff is deep in the operator loop
Yeah I was looking at code for my original example (which used generator.send) and I was confused about where all the increfs were :P
working on PEP 828 has taught me that the generator implementation is not fun
but I don't think you need to look at the eval loop here, you want Modules/_operator.c and Objects/methodobject.c
Imagine that there was a operator.calltwice that behaved exactly like operator.call except it calls the function twice instead of once. That should make it obvious why the argument needs to outlive the call to x
Modules/_operator.c lines 927 to 937
static PyObject *
_operator_call(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
if (!_PyArg_CheckPositional("call", nargs, 1, PY_SSIZE_T_MAX)) {
return NULL;
}
return PyObject_Vectorcall(
args[0],
&args[1], (PyVectorcall_NARGS(nargs) - 1) | PY_VECTORCALL_ARGUMENTS_OFFSET,
kwnames);
}```
If PyObject_Vectorcall stole the reference held in args[1] and the object was already destroyed by the time PyObject_Vectorcall returned, it wouldn't be possible to use PyObject_Vectorcall to make a operator.calltwice - at least, not without having the caller increment the reference count so that it wouldn't steal the only reference to that argument
The normal Python calling convention is that callers hold strong references and pass borrowed references to callees. That's pretty arbitrary. It could be flipped, and we could have a different calling convention where callees steal references by default and callers who want to keep a reference for themselves need to increment the reference count themselves before making a call, but that's just not the convention
Ah ok so it’s an interface thing, makes sense
-snipped question I can answer on my own-
I guess this can all be worked around by passing around a container and deleting from the container, so it’s not actually that important.
Haha METH
Sorry I am very immature
oh come on it is super fun!
anyway i'm here just for the idea of a hyperloop powered by hypercoroutines that are just async generators under the hood
I've been running a code quality tool on some stdlib modules and I think it found a real bug in _ pyrepl:
https://github.com/python/cpython/blob/cd5217283112d41c0244e2d96302cbe33f0b4cb1/Lib/_pyrepl/unix_console.py#L564-L570
This should be e.raw += e2.raw instead of e.raw += e.raw, right?
Lib/_pyrepl/unix_console.py lines 564 to 570
e = Event("key", "", b"")
while not self.event_queue.empty():
e2 = self.event_queue.get()
e.data += e2.data
e.raw += e.raw```
sure looks like it
I've traced the usages of that function and looked at the tests, seems we never use Event.raw so that'd be a "dead bug". Maybe not worth fixing, then?
a bug is a bug
even if it's only a distraction for the next time we look for problems, it's good to fix now.
Thanks, I'll open an issue and propose possible approaches for a PR.
I once raised an issue for a wrong function call in a bit of code that was practically unreachable in importlib
The function call got fixed, not sure if the code is reachable now though.
should typing.casts params be pos only? I think it'd be a bit of a performance optimisation
I think in the past we've been reluctant to spend too much time optimising typing.cast given that it's inherently unsound, so its use is somewhat discouraged
at this point it would also probably require a deprecation period to make it positional-only too, and the deprecation warning would make things slower in the short term
It could be a typing-only deprecation, right? i.e. your type checker would complain, but runtime would not
I'm not sure we've ever previously considered "your type checker has complained about this for years now" sufficient warning to make a breaking change at runtime
Well, there's already a precedent of typing.List, typing.Iterator and other PEP585 items being deprecated, but that deprecation not being very apparent
but we wouldn't remove them at runtime without first emitting a runtime deprecation warning for several years as well
yeah that's true
(also, type checkers don't complain about usage of the deprecated PEP-585 items, even though PEP-585 says they should 😆)
pyright does have a deprecateTypingAliases option (disabled by default...), and mypy seems to have nothing at all
Oh, I wasn't aware of the pyright option -- that's good
It seems like you can discover the fact that they're deprecated through ruff's UP035 rule. I hope something like that will be enabled in ty by default, even if it's an overlap
I'm guessing even with that option, pyright probably still doesn't complain about using typing.Mapping, though? Typeshed pretends it's the same object as collections.abc.Mapping, after all
This type is deprecated as of Python 3.9; use "collections.abc.Mapping" instead (reportDeprecated)
oh, nice
!pypi flake8-pep585
there's also this flake8 plugin 🙂
I wonder how it does that... I'm guessing it's just naively inspecting the AST (like the Ruff pyupgrade rules) rather than using the type?
I think it just has a hardcoded list of deprecated items, yeah
btw, ruff's up035 rule only catches itemized imports, so this is not detected:
import typing
def f() -> typing.Iterable[int]:
return [1]
huh
flake8-pyi does that better than Ruff then
there must be 1,001 linters that try to catch this
(https://github.com/PyCQA/flake8-pyi/blob/main/ERRORCODES.md#Y022 in flake8-pyi)
interesting, Y022 was added just a few days before the first release of flake8-pep585
so flake8-pep585 was low key a waste of time
flake8-pyi has only ever supported stub files, though
oh
Ruff's versions of the flake8-pyi rules often support .py files too, but I don't think Y022 was ever reimplemented, since it was considered redundant with the pre-existing UP rules in Ruff
If the performance of typing.cast is bothering anyone, PRs to optimize it out in the JIT are welcome 🙂
I think that's already taken by someone 😉. Though, it's a little tricky as it needs to play nice with the perf function "JIT" as well.
Who?
I think the dependency chain is that we need the PE pass first right? So that would require either me or Reiden to implement the PE pass first.
If we want to optimizeall simple Python functions, yes. But converting it to a C function and special casing it in the JIT would work. There are quite a few callables worth handling this way
as a never-nester, I love yield from. but is it accurate to say that if you, as the caller, don't intend to .send to or .throw into the generator, nothing will be any different for the caller than if the generator were written as for x in y: yield x (as opposed to yield from y)?
(corollary: the "coroutine" nomenclature makes way more sense when you think of functions as "subroutines")
what's a never nester?
someone who avoids having code at deeper levels of indentation
!clban 871340972653576233 spam bot
:incoming_envelope: :ok_hand: applied ban to @sharp comet permanently.
What did I do?
I didn’t even send a msg since like last to last week?
This is not refering to you, it's a different user. If it was you, you would be banned and unable to write here.
Oh ok
Thx tho
the only times i have seen people with real life needs concerned about cast performance the thing they are actually concerned about is the performance it takes to construct the first argument to cast (can be slow to make some big generic types)
what is cast?
typing.cast?
yes, scroll up to see the discussion
Wow
ruff has a rule to enforce passing a string as the first argument: https://docs.astral.sh/ruff/rules/runtime-cast-value/
So I ran a tool I'm developing to search for CPython bugs on _collections module and it thinks it has found 3 refleaks and 3 FT races:
- 3 reference leaks: add
Py_DECREF(item)on failure paths indeque_append_impl(381),deque_appendleft_impl(428),deque_copy_impl(631) - 3
default_factoryuse-after-free races: add critical section or atomic-load+incref indefdict_missing(2233),defdict_reduce(2305),defdict_repr(2373)
Anyone willing to take a look at the full report to see if these are false positives and/or check the other findings? I know nothing of CPython internals to check myself.
Hm, the tool found at least one real crash:
MRE:
import xml.etree.ElementTree
import gc
builder = xml.etree.ElementTree.TreeBuilder()
for x in range(10):
builder.start("a", {})
for x in range(10):
builder.end("a")
root = builder.close()
print(gc.get_referrers(root[0]))
Program received signal SIGSEGV, Segmentation fault.
0x0000555555b5ca97 in Py_INCREF (op=0x0) at ./Include/refcount.h:281
281 PY_UINT32_T cur_refcnt = op->ob_refcnt;
If anyone is interested in the community aspects/thought behind the JIT, I wrote a blog post https://fidget-spinner.github.io/posts/jit-on-track.html
It's not really a technical one this time.
Is using _testcapi.set_nomemory() in a script fair game for reproducing crashes that only happen on OOM situations? Or does it mean the report isn't useful?
does anyoen have a game idea? it can be 2D and 3D, ill be making it in python
anyone*
?
seems fair to me, but you could presumably also just do 'x'*100000000000 or something like that if you can call arbitrary Python stuff
wrong channel
Mb
!e hey so, fun thing...
import json
print(json.dumps({1: 1, "1": 2}))
This seems like a bug in the json.dump and json.dumps functions
:white_check_mark: Your 3.14 eval job has completed with return code 0.
{"1": 1, "1": 2}
This is mentioned in this bug report for duplicate keys https://bugs.python.org/issue45054
dang ok
Json only supports string keys
!cleanban @warm canopy 7d stop spamming your article
:incoming_envelope: :ok_hand: applied ban to @warm canopy until <t:1774419452:f> (7 days).
the problem is that it turns unique keys into non-unique keys
which is seemingly unexpected behaviour
The json spec doesn't require keys to be unique.
and also doesn't say what will happen if they are not unique.
Seems like the major complaint was overhead in the happy case. Maybe there’s a clever zero-overhead way to add the check?
Json has this problem where numbers become string
have you found that? When I use json, the numbers are still numbers.
I had an dict inside another dict (like dict[str,dict[int,str]]),and I saved in a json file.
The second time I ran my code to compare the json file I was getting an KeyError,the reason was that the key of int type for some reason become a string
So if I loaded this json it would become a dict[str,dict[str,str]]
ah, numbers as keys. Right.
!e
import json
print(json.dumps({1:1}))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
{"1": 1}
oh you've already run that today
check the types of the keys, and only do the duplicate check when you first encounter type(k) is not str. It's not zero overhead, but it should have hardly any impact on the happy path.
heck, perhaps dicts should have a flag that says whether all of the keys are str's. I think there's other places where the implementation needs to know exactly this...
I think it already has that, in fact - see this
Include/internal/pycore_dict.h lines 171 to 175
typedef enum {
DICT_KEYS_GENERAL = 0,
DICT_KEYS_UNICODE = 1,
DICT_KEYS_SPLIT = 2
} DictKeysKind;```
so all it'd take is for CPython to expose a private function for checking whether all of a dict's keys are str, and having json.dumps check that. That actually is pretty close to zero cost
seems worth doing to me but then again people seem to like having arguments about json parsing performance
I noticed the other day that the orjson maintainer turned off PRs and issues. Now I'm pretty sure the only way to contact them is via the email in the project metadata.
why do you think that is?
They already treated the repo with a very unusual open source approach that didn't involve much communication. orjson makes engineering decisions that I don't think are very good - it's written in unsafe rust directly against the C API for speed, but that style of Rust is very easy to get wrong.
is there any evidence that the code is error-prone or insecure?
I've been meaning to switch a lot of my code to use orjson so I don't have to do special handling of dataclasses or datetime.
No, no concrete evidence. I wouldn't want to deal with orjson as an upstream dependency though.
given their practices and communication style
I have some concrete evidence - which I could link to if they hadn't disabled issues.
https://github.com/ijl/orjson/issues/483 is a dealing I had with them, complaining about them drastically over-allocating their memory buffer and then not doing a shrink-to-fit
do you think they're preparing to mothball the project, or what?
no, I think they just didn't like dealing with issues and PRs
oh hey orjson will support free-threaded Python in 3.15
huh
<@&831776746206265384> maybe scam or at least spam ^
!cleanban @hollow owl giveaway scam
:incoming_envelope: :ok_hand: applied ban to @hollow owl permanently.
...why does orjson have issues disabled?
There is no open issue tracker or pull requests due to signal-to-noise ratio.
https://openai.com/index/openai-to-acquire-astral/
I figure that someone needed to buy Astral for their "giving away free OSS" business model to be sustained. But I'm apprehensive at the same time.
And here's astral's side of it. https://astral.sh/blog/openai
Cynically, I'm wondering how long until the enshittification begins. ** But, Charlie has been good to the community, so I'm optimistic he'll fight the good fight.
At least it wasn't a horizontal acquisition. Those usually happen to kill the competition.
my guess about the two most likely bad outcomes: 1) neglect of uv and ruff, 2) defensive forks of those and the split and confusion those forks would cause.
Maintainer isnt all that great
i saw a message here a while ago where a user opened an issue for a very real problem, iirc something to do with serializing a certain type and the maintainer marked it as spam and closed it
I've reported a real issue too (overallocating by a factor of like 9x, so that ~100MB was allocated for a ~10MB payload, IIRC - but I can't check that without being able to view the issues!).
It was closed and locked without any discussion.
signal-to-noise would be higher if they didn’t treat people so rudely
I trust the Astral team. They will do a lot of right things. But of course that isn't always how life works. The investors (owners¿) have a lot of power and pressure. Only time will tell 🙏
I'm trying to tightly integrate 3.14 GIL-less Python with Godot Engine. To eliminate the need for marshaling and make (Godot) Object zero copy passable via GE-Python boundary. The problem is that Object is virtual so it will have vtable at the start and PyObject_Head only after that (if Object will inherit from PyObject). Static casting down to PyObject* should give Python correct pointer (PyObjectType must mind that offset). The issue is with allocation - GIL-less requires to allocate Python Object using mimalloc. Correct allocation size seems trivial but that offset to PyObject_HEAD is problematic; C++ side frees Object by calling memfree on them and this is hijacked to instead participate in Refcounting.
Sounds interesting but I think I need more info to help. I do know that CPython expects PyObjects to be allocated via PyMalloc, so using your own allocator probably won’t work in general…
Also don’t understand why the PyObject header has to come after other stuff
e.g. see the note at the bottom
of this section https://docs.python.org/3.15/c-api/memory.html#allocator-domains
Itanium C++ ABI enforces that first vtable will be at offset zero (and push PyObject_Head).
If you come to the free-threaded Python discord (linked here https://py-free-threading.github.io/#news-and-getting-help) we can ping some of the core devs who know more than me
The free-threading guide is a centralized collection of documentation and trackers around compatibility with free-threaded CPython for the Python open source ecosystem
that said, I doubt anyone has thought much about itanium support
a little surprised that’s important to you in 2026
Alternative is to use struct with PyObject_Head and pointer to Object that adds indirection or embeed entire object that will mess polymorphism; both will make c++ api awkward
I think Clang uses that Itanium spec to reorder Object memory layout and put vtable at the start.
oh maybe I’m misunderstanding what you’re saying
For virtual classes memory layout will be reordered such that first vtable is at offset zero - so PyObject and Object : public PyObject { virtual ~Object(); } will not be layout compatible
For my case static_cast between Object* and PyObject* gives correct pointers for c++ or Python (simple offset).
does nanobind or pybind11 handle this?
GIL-less Python introduces requirement that only Python allocators will be used to create Python objects - can be wrapped but still must eventually use them.
It is fine if mimalloc allows to put extra memory before PyObject_HEAD for that vtable.
CPython itself puts extra stuff before PyObject_HEAD sometimes - the gc head goes before the PyObject*
See PyGC_Head
Nanobind doesnt. I'm using it to create PyObjectType. Intrusive refcounting is for exposing Pythin refcounter to c++ side. I don't think there is anything to tell nanobind wrapped class is PyObject.
So this would mean I need to manually patch some of Python machinery to be mindfull of that vtable?
also have you looked at making your types heap types?
and put the polymorphism in the Python side instead of the C++ side
heap types support multiple superclasses as extension types
I'm not totally sure I understand the problem you're facing, tbh. I'm just giving you a piece of info you didn't seem to have: that the PyObject* that PyObject_GC_New gives you will point to the middle of a mimalloc allocated block, not the start of it
I’m not aware of anyone doing what you’re trying to do, you may very well be the first person to try…
the core of this problem is to have virtual class that is both valid in c++ and Python
It is likely.
The easy way to do that is to make it not be virtual in C++, and to instead make the vtable manually so that it's part of your object's footprint
But of course then you're doing any polymorphism manually, and dynamic_cast and such won't work
Considering that every PyObject itself contains a vtable already, though, it feels weird to have a design that adds a second one. Every PyObject contains a pointer to a type object, and the type object holds the vtable for all Python dunder method dispatch
which is what I was saying about doing the polymorphism on the Python side of things, I think
also: do your objects ever move in memory? PyObject isn’t supposed to move.
Another problem is that I do not control Godot Engine code. Any new Object derived class will have to be manually integrated. Using struct trick will require rewrite of almost everythng.
They don't
I've just found this:
/* Test a `new`-allocated object with a virtual method.
* (https://github.com/python/cpython/issues/94731) */
class VirtualPyObject : public PyObject {
public:
VirtualPyObject();
virtual ~VirtualPyObject() {
delete [] internal_data;
--instance_count;
}
virtual void set_internal_data() {
internal_data[0] = 1;
}
static void dealloc(PyObject* o) {
delete static_cast<VirtualPyObject*>(o);
}
// Number of "living" instances
static int instance_count;
private:
// buffer that can get corrupted
int* internal_data;
};
from Lib/test/test_cppext/extension.cpp
Looks similar to what im trying to do - at least that static_cast
that does look exactly like what you want, except it allocates the memory with new rather than PyObject_GC_New - I wonder if it works with PyObject_GC_New?
I actually didn't think it was legal to allocate a PyObject with new, at least not in the free-threading build...
I think this is illegal in free-threading build to bypass default allocators that do more then just allocate
In free-threading build allocators also are used for bookkeeping in their slow path. Also that page mechanism is used to prevent (I think) some sort of use after free (I think) for deffered refcouning.
the FT build needs to use mimalloc for allocating objects because that's how it finds objects using the GC. but, if the object isn't tracked by the GC anyway, it doesn't matter
oh yeah, that too, but deferred refcounting doesn't work on non-GC objects either
Another reason for making Object inherit PyObject and tightly integrate it with Python was so GC can be detected across GE-Python boundry.
There was a project to manually manage reference counting on boundry via CTypes but it never was finished. I've tried finishing it but now I get why it failed.
Maybe there’s a way to add a hook for this in CPython? Probably would need a fair amount of discussion first though. And I doubt it’d be backportable to 3.14t.
I'm fine making manual patches to Python code.
The main issue is for patches to be quite small (even if hard) and maintainable.
Most common ways of dealing with this problem require rewriting a lot of Godot Engine code I do not control - so many manuach adjustment with each relase.
even though I'm still very angry and sad that my pycon talk was rejected ||it's fine||, the schedule of AI-related talks (those are the ones I mostly need to go to to justify my attendance) is looking very good
What was your talk
it was gonna be about using docker.py
It's generally not practical. I was involved with selecting talks for PyCon this year, and there was just a huge number of submissions. (I wasn't on the track of Stelercus's talk so don't blame me 😄 )
there's a couple, e.g. whether the talk description is well-written, whether it's relevant to the conference, whether the outline is realistic
Dan Shernicoff wrote a blog post about it which I like: https://brassnet.biz/blog/picking-talks-for-pycon-us.html
In which I talk about how talks for PyCon US 2026 were picked.
Why blame 1? It doesn't error when if condition is parenthesised. But i think it still can work without issues even when no parenthesis in this situation, What is reason?
I doubt that was intentional, it's just sometimes hard for the syntax error machinery to find the right place
Feel free to open an issue on CPython to improve this
That would be my first cpython issue xD. did no one found this before?
maybe? search the issue tracker. If not, maybe everyone else who hit it figures they couldn’t possibly be special enough to trigger a bug in CPython. CPython has bugs!

I will wait a bit for others to reply to this.
There's a large surface area for bugs like this, your code is a little unusual, and I could imagine people seeing it and not thinking about it being buggy, so not altogether surprising if nobody reported it before. I'd search the issue tracker for things like "SyntaxError walrus" and if you can't find anything, please open a new issue
Understood. Thank you so much.
Oh cool. I did not see your message earlier. Is this commit targeted for python 3.15+?
Looks like it, backporting was considered, but abandoned.
Couldn't find an already registered issue for that
Thanks Jelle & ngoldbaum for suggesting submitting an issue!
I thought that was just way we had to do it, but the PEP does not make it appear that way
Not sure if this is an appropriate channel to ask this:
Why does __import__ exist? What's the appropriate & recommended usage for this in what context? Especially over importlib. Is it a Python 2 leftover?
the biggest reason to use __import__ is if you need to import a module in an expression, as opposed to a statement
you could use importlib.import_module, of course - but only if importlib has already been imported
I don't think I've seen that need outside esoterica though
perhaps dynamically importing a module based on names determined at runtime? but that's also a bit cursed
I'm sure there are usecases
you can use it to customize import statements
for various esoteric and dubiously-exoteric purposes
I'm not sure we'd add __import__ if we were adding import statements today, but there hasn't been any reason to remove it
I didn't add a __build_type__ or anything when I added the type statement
__import__ was a nice way to break some attempts at sandboxing Python, so it's at least didactic 🙂
I used __import__ for a dynamic lazy import tool
For one, it saved an import for something that's supposed to be about saving imports - but also I had submodule imports where I wanted the base module.
!e
from importlib import import_module
print(__import__("collections.abc"))
print(import_module("collections.abc"))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
001 | <module 'collections' from '/snekbin/python/3.14/lib/python3.14/collections/__init__.py'>
002 | <module '_collections_abc' (frozen)>
__import__("collections.abc") returns the collections module exactly because import collections.abc binds the collections name
Importlib isn't about saving imports. It's about dynamic imports
is there an easy way to get uops like in CPython/Lib/test/test_capi/test_opt.py? I don't see anything obvious in dis, but also I've only checked by ctrl-f-ing the page with stuff like "micro op" and "uop"
you can set the envionment variable PYTHON_LLTRACE, 1 -5
I can't remember if you need the debug build too and whether you must build with tier 2 interpreter only
thanks, my build is debug with the jit compiled, so I'm not sure if it needs it either, but it works with my build 👍
It seems like the JIT compiler applies some bytecode transformations before generating machine code using the copy-and-patch method, although I'm having trouble finding where these transformations are located. does anybody know where these are?
this might help https://github.com/python/cpython/pull/146110
thanks again
Oh yeah, there's mostly two major parts in 'transforming' bytecode I think:
- Specialising adaptive interpreter
- The JIT optimizer. In
optimizer_bytecodes.cguards are removed/replaced and a new optimized trace is formed and later jitted
hmm, so the normal interpreter runs, except has counts for jump_back or resume (like a frequent jump to a region of code). if a threshold is reached, the specialized tracing interpreter is invoked.
then tracing interpreter records a trace (maybe it runs the optimizer_bytecodes?), which produces uops. the uop trace is then passed to an optimize function that produces an optimized uop sequence. there's an assembly stencil for each uop and there's extra "patching" which is basically just GHC linking or something
at least that's how I understand it
and basically, the current work is mostly on:
- making sure the uops are in a form that is more optimizable, which involves modifying the bytecodes a bit to be composed of uops
- looking at the compiled stencils produced by llvm and identifying any big issues, and presumably there's a bit of fiddling there, but that mostly funnels into 1.
- work into
_PyOptimizer_Optimizeas optimizing a somewhat traditional IR
does python have a 3 layer IR system?
- "instructions" as part of an "instruction sequence"
- bytecodes
- uops
can all of these be represented using a normal textual IR in a sane way, or are they too coupled with python and/or generally unstable? (e.g. it's hard to make any assumptions on a generic BINARY_OP of variant + as it could require the lookup of an arbitrary __add__?)
specialized tracing interpreter is invoked.
Both the specialising adaptive interpreter and the trace recorder some sort of threshold
then tracing interpreter records a trace (maybe it runs the optimizer_bytecodes?), which produces uops. the uop trace is then passed to an optimize function that produces an optimized uop sequence.
yep that sounds right to me
here's an assembly stencil for each uop and there's extra "patching" which is basically just GHC linking or something
I'm not sure unfortunately
making sure the uops are in a form that is more optimizable, which involves modifying the bytecodes a bit to be composed of uops
yeah, a bytecode can do multiple things like guards and the operation itself, splitting them up allows the optimizer to remove certain stuff when we can prove something, like a known type
-# To my knowledge anyways *
No the specializing interpreter is almost always on (it triggers after 2 executions, so practically almost always)
bytecodes is public and can be accessed through Python but semi unstable, uops are internal and completely unstable
I know why it does it, the use case was that I wanted to import a submodule but get the parent module. In this case __import__ returns what I needed, while import_module does not.
I was referring to the dynamic lazy import tool I mentioned, which was about saving imports. Using __import__ for a tool meant to save unnecessary imports made sense.
That said importlib is a faster import now than it was when I made the tool
It used to also import warnings, looks like someone changed that in 3.13
thanks, is it that the specializing adaptive interpreter itself is the one that traces, but only when it's in "tracing mode"?
is this done primarily as the specializing interpreter has overhead that's avoided if something's only ran once? like the normal interpreter can handle imports and the such?
thanks, do you know anything about the "instruction"s listed in step 3 here https://github.com/python/cpython/blob/main/InternalDocs/compiler.md, and in Python/compile.c? were these also just the bytecode used lower down, or was it something else?
Certain bytecode have an internal counter, and when they get executed, it gets incremented. For instance, if a BINARY_OP, adding two integers, has reached the threshold, it will be marked as adaptive. The next time this executes, it will adapt into a BINARY_OP_ADD_INT, which is the specialised version. So it doesn’t trace by literally adding traces of bytecode unlike the trace recorder.
Is it possible to make the KeyError error message more informative by default? For example,a dict has a key "1" and the code tries to access 1,instead of KeyError: 1,it says KeyError: 1 not found,maybe you want '1'?
interesting, thank you
That sounds like it would make KeyErrors way more expensive
Since it must scan through all other keys to find close matches, however we define that
it'd only need to do it in the repr, which would more or less be equivalent to only when the exception wasn't handled
Oh, good point
you could even do it only when the process is dying to that error or returning to the repl, if you really wanted to guard it
frankly, I wish the __str__ of a KeyError included anything but the key. I'd find it a massive improvement if it even just said KeyError: no such key: 1
!e print(KeyError(1))
:white_check_mark: Your 3.14 eval job has completed with return code 0.
1
that's just weird.
but I imagine there's backwards compatibility reasons why it must remain that way
do other errors that include a value use "" or `` to surround the value?
hmm, interesting inconsistency here with types
>>> "" + 0
Traceback (most recent call last):
File "<python-input-6>", line 1, in <module>
"" + 0
~~~^~~
TypeError: can only concatenate str (not "int") to str
>>> {[]: 0}
Traceback (most recent call last):
File "<python-input-7>", line 1, in <module>
{[]: 0}
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
>>>
in the first error, it uses "" to surround the type, but in the second error, it uses ''
can't think of an error that actually spits the value back out tho at the moment (besides KeyError)
Yup that's exactly it, it's just one interpreter in the implementation
Yeah there's only 1 baseline interpreter
Nevermind kenjin said it before me, didn't see the explanation since discord started me on kevin's message
Is there a publicly documented stance regarding readline-y features of the new REPL?
I'm currently either frustrated by the new REPL or setting PYTHON_BASIC_REPL=1 because it's missing at least one shortcut I frequently use (ESC _, for inserting the last word of the previous line).
instructions = bytecodes in the compiler are used interchangeably
sorry for the sorta vague questions here, but would it be fair to say that the current CPython JIT compilation is quite similar to the V8 sparkplug jit compiler?
I believe they do similar things, but the compilation is different. CPython uses copy-and-patch while sparkplug uses dark magic I think
yeah, it seems sparkplug just... goes straight to machine code?
no IR or anything
We're trying to nake it more similar level to a mid tier (so maglev) whether we actually achieved that yet is a separate matter 🙂
They are almost the same but in practice do the same thing no?
Predefined assembly templates inserted into executable memory based on the current operation
Wouldn't you have to ditch copy and patch for that? Maglev constructs assembly based on an intermediate representation (turboshaft) and doesn't rely on predefined templates to my knowledge
We have our own IR, the copy and patch backend is just used to generate machine code
Hmm
I recall that each template is output based on which micro op the compiler is currently generating for
Wonder if that's fine grained enough for the heaviest optimizations
I guess we'll wait and see whether the team decides to keep the current implementation or do what PHP did, hard to say at this moment
I think they’re functionally similar, yeah
I don't really understand your point? The IR is optimized and emitted using a data-flow analysis pass. The only difference is that the PHP passes implement more optimizations and also a more powerful abstract domain.
It's not that it goes straight from bytecode -> uop -> machine code, there's actually optimization passes in the middle.
This is what I was thinking, however I still don't have the fullest picture of the compiler and I'm mostly asking to get more perspective.
I'm not sure if the uops are in an ssa format, but other than that my understanding the cpython jit is very similar to the maglev compiler in the optimization passes, and very similar to sparkplug in the codegen
That's a better description of it than mine yeah
Hi, I hope this is the right place to ask. I have a PR https://github.com/python/cpython/pull/133276/
How can I get a reviewer (it's in the http module)?
I've already tried https://discuss.python.org/t/http-expert-reviewer-needed-for-pr-133276-gh-42550-add-expect-100-continue-support-to-httplib/106356/2
now that's the oldest issue I've ever seen
!warn 1260254962789777470 Do not advertise anything here, including telegram channels.
:incoming_envelope: :ok_hand: applied warning to @native hawk.
We have a few older ones
Any tips on how I can get people to take a look at it?
ZeroIntensity, who frequents this channel, apparently looked at that PR in the past.
I was just doing a high-level triage review, fixing docs and stuff like that. i'm not nearly enough of an expert on HTTP or http.client to confidently review that
Part of me would rather not add features to http. Writing a good HTTP client library is hard and the standard library isn't really the place for it.
I understand that, but to me this seems like a bug fix more than a feature addition. Am I looking at this the wrong way?
Maybe but it also adds a new parameter. I feel like implementing more of HTTP naturally leads to more code to maintain
<@&831776746206265384> scam ^ (now deleted message, Jelle isn't scamming)
!pban 1486595953531027456 scan
:incoming_envelope: :ok_hand: applied ban to @arctic totem permanently.
!otn a jelle's typing scam
:ok_hand: Added jelle’s-typing-scam to the names list.
I worry about PEP 789 I think it's too niche and too difficult to use
!pep 789
Like it's absolutely crucial to be implemented
FastAPI had been yielding in TaskGroups in the SSE code
And it's terribly difficult to explain why you can sometimes yield in a TaskGroup and sometimes can't
And I don't think it's documented anywhere
the inner workings of task cancellation is forbidden grey beard knowledge
Pretty sure that won't make 3.14! Seems unlikely for 3.15 too (40 days to feature freeze), seeing as the authors never opened the discussion thread in June 2024, so I don't know what's going on there. I'll ping them.
my EuroPython talk on the CPython ABI was accepted 😎
just bought tickets for Poland 🙂
Given the issue has been open since 2005, still worth fixing? I ran into this at my previous workplace, where we end up wasting bandwidth as a result. Other languages support this in their HTTP clients and this should be backwards compatible. The bug report also calls out that this causes failures with Apache SSL renegotiation. Anything I can do to address your concerns?
I don't have anything to add, except to say thank you for your work. it seems like very few people are knowledgeable enough for your PR to be reviewed quickly, and you've been pushing this valuable work for awhile 👍
reply about PEP 789:
We ended up blocked by the lack of a working prototype - and a few weeks ago, Claude managed to write me one! Which I haven't reviewed carefully enough to trust at all yet to be clear, but I think we're on track for a sys.monitoring--and-monkeypatching backport in the next month or two.
Given the tight timeline for 3.15 and the lack of any practical experience with my proposed design, I think retargeting to 3.16 is the way to go. I'm aiming to open the discussion in the next few weeks, once the implementation is ready for people to try out.
Where did you see this?
What's the policy on Claude et al in CPython?
Ah found it
Looks slightly outdated
it'll always be outdated!
Nuh uh, eventually there will be a CI job that automatically updates the policy
only dutch AIs allowed
I use Claude Code always supervised by me
They've recently beaten into it that tests should always assert the state of the code
But now I'm hitting points where it's generating tests I want to pass and I do want the code fixing
Click here to see this code in our pastebin.
is this a joke man 😭
It's a joke and semi serious
Sorry I didn't do a good job explaining it, my main concern is that the current implementation in Python might be limiting compared to other heavy duty Just-in Time compilers since its backend consists of preconstructed assembly templates, meaning the granularity of the instructions it can assemble is coarser than regular compilers which can go down to the level of choosing single instructions - In our case we can't do that, the lowest we can go is the sequence of existing native instructions that make up a given micro-op handler
I did not mean to imply that Python simply turns bytecode into native instructions without transforming micro ops, sorry
I was just wondering out loud whether in the future the core team might find the current implementation too limiting and switch to a more traditional backend like PHP, but I realize now that it would be a significant undertaking that would likely need the backing of a company with a large pool of resources to make a reality
Well, either that, or one borderline insane genius who can write the entire backend infrastructure of a compiler by himself or herself, there's definitely people like that out there in the world
From the research I've done, there's also many benefits to the current approach. The current technique (copy-and-patch) is really fast, this is nice in a world there multi-tiered jit compilation is normal. If the maintainers wanted better optimized code, they could add another jit compiler on top of the current one. Then they would have both a fast and slow jit layer
mike pall
Good point, multi tier systems are the way many runtimes have chosen yeah
Yep, and I guess Cliff Click, but beyond that I'm out of names
Thanks for explaining. To answer in short, yes copy and patch does have its tradeoffs. There was a contributor recently who copy and patched to dynasm instead of machine code and saw the JIT double in speedup https://github.com/python/cpython/pull/146235
Good lord!
All the person did was change the backend to DynASM and nothing else? That is incredibly impressive
that is super cool
It's claude assisted I think, otherwise I think this would take a lot more effort
I see
DynASM is pretty good, that's what PHP uses (For both their old no IR compiler and their new one I think)
Actually there's one thing I never found an answer to, is the existing Python implementation a function level compiler or a tracing one?
I wonder what benefits would come from copy and patching to llvm IR, and running its "jit". it would definitely be way too slow for real world use, but it could be quite fast
FWIW, I have looked at Dmitry's IR for PHP even before it got upstreamed
Tracing with some function characteristics is what i like to think
PHP has both method and tracing and I was informed the tracing one performed better for them last I contacted them
I think you may not want to copy and patch when you use LLVM JIT, LLVM bitcode is simple enough compared to assembly that you can write a backend for it without having to compile C snippets to it
That said, yeah LLVM compiles far too slowly for its JIT to be used by pretty much anyone
I remember some post or something where moving to a method jit was brought up as a worst case scenario
Last I remember that's why PyPy doesn't use it, PyPy also mentions that LLVM JIT has other severe limitations not related to speed as well
To be clear: they also did add some opt passes in the backend (documented in the PR) to do some nice whole-trace machine code optimizations
Ah alright that makes sense
It's really hard to tell which gives assembly that has the better theoretical maximum performance, I think the tradeoffs and speed differences might be so close that there may not be a heavy difference if the implementation for both was absolutely perfect
hmm, iirc ruby's jit moved to a method level jit recently, right? but before they were doing something odd with basic blocks or something beforehand
Not sure, haven't really followed Ruby too closely
One thing that I realized some time ago from looking at systems like HotSpot or V8 is that when dealing with a language that doesn't have the best semantics for performance, you can't just rely on the compiler for maximum performance, the entire runtime needs a huge amount of optimization as well
The compiler is a huge part of the picture, but not the only part
im not sure if they're completely moving over, my own speculation is eventually the method one will be ruby's higher tier jit, while ruby's lazy basic block versioning jit is still around for fast warmup
again just my speculation, might be completely wrong and i have no clue of their plans
since I didn't look too closely into it, I'm almost certain you're more right than me
At least from what I witness the strategy heavy duty runtimes have is to ironically make the implementation such that runtime operations are done as little as possible and most of the execution time is in the interpreter handlers or compiled code
well if it helps, i contributed a single PR to ruby's new zjit :), tho that was just a drive by to understand their system better
That's cool
yes that's a very fair assessment, it's why i also speculate pypy will always beat cpy until we change the C API
I've wanted to do something similar and write a compiler for Java to learn the process but only a few days ago realized that the JVM Compiler Interface is only ever exposed to Graal and end users can't access it, so there go my plans :(
I guess it's for good reason but it's kinda sad
From my observation PyPy is sort of a one trick pony that put all the effort into the Just-in Time compiler and nothing else so I'm not sure if that's the reason it can do better than cpython
There are reports out there that once its compiler is shut down for benchmarking purposes it actually does worse than cpython (Both purely interpreted)
I hold pypy in much higher regard :). They did optimize their runtime quite abit (collections, cext, etc) but my understanding is most of the runtime opts are to benefit the jit not the interpreter. The interpreter just exists to handle what the JIT cant, which is fair
Hmm, perhaps I didn't look in the right areas for PyPy to find the optimizations
I did focus more on the interpreter than the compiler since their design (An automatically generated compiler that compiles the interpreter itself rather than the running program? Still not sure what's going on there) is a bit unconventional and hence not easy to understand
Your description seems correct based on what i know. PyPy's JIT trace optimizer is a semi work of art. I looked at it for a few months.
Also, what's this whole partial evaluation pass about?
From what I understand through reading the issues, it's something related to specialising a programe or in this case maybe a trace/uop? And possibly optimize away temporary objects like tuple unpacking and apparently float unboxing too?
Then there's more on "shadow stacks" and static/dynamic and non-escaping/escaping instructions...
It's just a fancy name for saying if we specialize a program with respect to known static information, we get a faster program.
static information in this case can refer to almost anything
I am of the opinion that an automatically generated compiler can't beat one that was conventionally written by an expert compiler designer, but I do agree with you that PyPy's compiler generator is really well put together (Well, I do wish the code was neater, but it does what it was designed to do very well)
Do escaping instructions mean instructions that refer to objects that escape the current function scope or something else?
eg a useful example is
for k, v in dct.items()
normal cpython has to create a tuple on every iteration just to immediately unpack and throw it away
if you do some escape analysis, you can see the reference is unique and that it is immediately consumed, thus the tuple allocation can be avoided and we just put the dct items k,v onto the stack
i think so? not entirely sure
ohh
I think we could bypass even the stack
the top of stack is already bypassed in the jit and put into registers, so its already doing that
it was a few months back
Sweet
but idt it was in 3.14
3.15 in the section on regalloc https://docs.python.org/3.15/whatsnew/3.15.html#upgraded-jit-compiler
Is it only the top of stack that enjoys this benefit? Ah alright I'll give that a read
Yes locals are hard to do so correctly without an actual lifetime analysis. x = y is potentially escaping because people expect dunder del to be called immediately on x if it's overridden
and del can run arbitrary python code
in pypy this isnt a problem cos it doesnt use refcounting
I have several ideas swimming in my head but I realize the suggestions may be hard to do in Python
Hmm, I might go back and do these ideas as a prototype when I have time
Runtime performance in any language interests me so much
How low level we are willing to go is an open question I suppose, for the interpreter you can do top of stack caching like we already do according to that link, but you can also exploit the native stack, but this requires at the very least least going down to the level of LLVM bitcode and makes the design so much more complex
I realize talk is cheap, so perhaps that's one idea for myself to try out in the future
I feel like I'm in a similar position, but I still don't know enough about the cpython architecture to even float around ideas
I don't know cpython internals that deeply either actually, which is why I'm not sure how feasible some of the ideas are
I went to study the code in the meantime, which is a rather slow process when you're coming into it blind
Majority of the stuff I did with Python was to allow Windows to use the configure build system and gcc/clang (To help out the mingw-w64 folks) rather than actual work on the runtime itself
Toy interpreter example I hacked together, took quite a lot of hours but hopefully demonstrates going lower level to get better control over things like the native stack
Unfortunately however LLVM bitcode stack pointer modifications are turned into terrible suboptimal instruction sequences
I have not yet figured out a way to get around this
I will continue refining the toy into something more optimized and workable
I don't know why I wrote that bitcode by hand, that was pure suffering
I generally tend not to write interpreters with indirectbr (computed goto) anymore and use the musttail instead. It often generates better assembly corresponding to what I want, also the interpreter state is kept in registers/hot stack slots.
Don't they both yield very similar dispatch instruction sequences? indirectbr was easier to implement in bitcode and slightly more compact given I am not an expert in bitcode syntax in this case. I could try using the musttail call bitcode syntax instead and see how that goes
I found it's not the dispatch, it's:
- Indirectbr has no control over what to put into registers if your interpreter state consists of many variables. in C, we just trust the regalloc.
- There're some code locality issues with indirectbr once your interpreter gets huge. PGO solves this partially, but not always. The main thing is that it's an entire single large function, on CPUs with smaller cache, I've found musttail with it's individual small functions perform much better than a single large function with an indirectbr.
Ah, I see what you mean
I think when at the level of bitcode you can still hand optimize indirectbr to make most things go into registers (Definitely can't do this in C) but that second one is a big issue I had not considered when I undertook this effort
Hmm
FWIW, the cache thing only affects CPython because of how big the ceval.c is, if you're writing a toy interpreter, you can just ignore it 🙂. Also, I think for small interpreters, indirectbr might be faster.
I did also read through the source code since the last conversation and it seems like my idea stealing the JVM strategy of using rsp instead of allocating out own stack isn't going to work, unfortunately
Well, I am doing this as an experiment for potential contributions to cpython, so there wouldn't be much of a point to continue what I'm doing if it can't be extrapolated to Python :P
We could I guess switch Python from token threading to direct threading, but the improvement would likely be tiny and not very helpful
After that I'm fresh out of ideas
Yeah I thought about switching too, but we need to preserve the bytecode API for libraries like PyTorch, and we'd then need to store an array of function pointers instead of bytecode, which would be more memory for maybe a low gain.
There're some pretty exciting ideas I want to try in 3.16 for the JIT
That's good, hopefully they work out well
If you have any more ideas, they're always welcome! A lot of the things you've thought before I've also given some thought 🙂
What I can think of is what the author of the blog post I linked here some time ago told me in private emails, but I think that would be things that you already know
When the JIT takes over, the second biggest time we spend is not the interpreter anymore, but rather memory: https://github.com/savannahostrowski/pyperf_bench/blob/main/profiling/jit.svg
So anything that avoids allocation of objects would be a huge win. This is where things like this will help.
He mentioned you need everything in registers, far more than even what cpython is doing on the latest main branch, and avoid memory accesses like they're cursed due to things like store forwarding (I may be remembering the term wrongly)
I also want to do some hot-cold splitting of the JIT stencils https://github.com/python/cpython/issues/143158
I suspect that should get us most of the perf wins you get from writing handwritten asm
In fact, the author of that dynasm copy-and-patch target uses hot-cold splitting in their PR by modifying what I described
That resembles what gcc does sometimes in compiled code that I've seen before, that should help quite a bit (Hopefully, you will never really know until you test it)
I should probably check if the tier 2 executors had the free threading bug fixed so I can pull main in for a bugfix, that does remind me, hang on
Which FT bug? if you mean FT support for the JIT, we haven't landed that yet 🙁
I think the biggest win is eliminating a single branch in most cases with hot-cold. Because if you arrange all the hot code stencils in a line, you should eliminate all the bad branches to cold code that (in theory) are infrequent.
Yeap I was referring to that :(
Can someone help me understand a bit better the python interpreter?
I am taking a look at string tokenization (Parser/lexer/lexer.c) and I really don't know why python is checking for errors when reading the string.
/* Get rest of string */
while (end_quote_size != quote_size) {
c = tok_nextc(tok);
if (tok->done == E_ERROR) {
return MAKE_TOKEN(ERRORTOKEN);
}
if (tok->done == E_DECODE) {
break;
}
I have no clue what can cause tok->done to be E_ERROR
and the only thing I know that can cause tok->done to be E_DECODE is if there is a UTF8 (depending on the encoding) error, like a malformed character. But if that is the case why is it being checked if tok_nextc() can cause tok->underflow() which buffers a new line and it checks for UTF8 errors there.
try commenting out those lines and run the CPython test suite
isn't the case of putting a null byte in the source code handled by that statement, or?
no, that is done by tok_nextc()
does _PyTokenizer_syntaxerror not do tok->done = E_ERROR, which that branch handles by propagation?
I am running the tests
yes, exactly that
Yeah, the flow is that the string scanner calls tok_nextc, which may raise a tokenizer error via _PyTokenizer_syntaxerror, which calls _syntaxerror_range which may set tok-done = E_ERROR here: https://github.com/python/cpython/blob/4d0e8ee649ceff96b130e1676a73c20c469624a9/Parser/tokenizer/helpers.c#L61
So once everything goes back up the stack to the string scanner, it needs to handle an error code
Parser/tokenizer/helpers.c line 61
tok->done = E_ERROR;```
ok, done
23 tests failed:
test.test_os.test_os test.test_os.test_posix test_dbm
test_dbm_dumb test_dbm_gnu test_eintr test_filecmp test_glob
test_http_cookiejar test_import test_logging test_mailbox
test_netrc test_pathlib test_profiling test_py_compile test_shutil
test_socket test_socketserver test_source_encoding test_stat
test_tarfile test_unicode_file
452 tests OK.
Total duration: 25 min 32 sec
Total tests: run=49,033 failures=108 skipped=2,718
Total test files: run=520/502 failed=23 skipped=22 resource_denied=5 rerun=23
Result: FAILURE then FAILURE
make: *** [Makefile:2478: test] Error 2
ok after some testing:
the string stuff that checks for E_DECODE isn't really for single quote strings but is for triple quote strings which allow new lines.
If a malformed character is on a new line. when tok_nextc is called at the start of the string quotes it wont catch the malformed character (bc its on a different line and tok_nextc only checks for character errors on the current line) and that't why it needs to check after reading each character
on normal strings bc new lines aren't allowed when python reads the starting quote tok_nextc will check for malformed characters on the whole string, which makes the E_DECODE check not necessary.
Note: If a single quote string is alone on a line and contains malformed characters python wont generate a string token(STRING)
but if on a triple quote string there are characters on a line before a malformed character, python will generate a string token.
even if the triple quote string isn't closed
It appears that dropping down a level below C to use the native system stack isn't possible, unless a redesign of the runtime is done we'll have to keep emulating the stack
There goes another idea up in flames
Coffee Compiler Club meeting on Mar 20, 2026
Typical rambling conversation loosely related to compilers, language runtimes, GC, performance and optimization.
Google docs: https://docs.google.com/document/d/1rA3N5cT9y_xeBjTZyJ380o4bPwxToN3epxOCgxpXwfY/edit?tab=t.0
[Started recording time] - please subtract the time in order to match the timin...
I wonder what could've been if the answer to the question he asked at 31:04 had been yes all those years ago
why is this done when tokenizing?
/* Peek ahead at the next character */
c = tok_nextc(tok);
tok_backup(tok, c);
to peek into the next character, probably when the following process/code requires consuming from the starting character instead of the next character after the token starts
although considering where i found that specific code snippet, i'm not sure
Parser/lexer/lexer.c lines 636 to 638
/* Peek ahead at the next character */
c = tok_nextc(tok);
tok_backup(tok, c);```
The if (tok->async_def conditional no longer survives, but the peek that was added for it still does. Looks like a mistake to me; that peek used to do something useful but wasn't removed when the code that was using it was
well, with a bit more context:
/* Peek ahead at the next character */
c = tok_nextc(tok);
tok_backup(tok, c);
again:
tok->start = NULL;
/* Skip spaces */
do {
c = tok_nextc(tok);
} while (c == ' ' || c == '\t' || c == '\014');
why read a character, then go back, just to read it again and check for spaces
python doesn't handle grapheme clusters right?
>>> "ááááá \x"
File "<stdin>", line 1
"ááááá \x"
^^^^^^^^^^^^^^^
SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 56-57: truncated \xXX escape
>>>
As there are more arrows bc the "á" is 2 code points
Serhiy recently added unicodedata.iter_graphemes(), and there is a PR to use it in traceback.py: python/cpython#142529. It would fix your example.
Is that real?
yeah, try it
ukraine's country code is ua
so if you reverse the codepoints you get au
my main purpose in this channel is to teach @boreal umbra cursed python trivia
is the python interpreter made with python?
#internals-and-peps message
The official and most widely used interpreter, cpython, is made in C.
I'm guessing you mean the standard library (which includes stuff like unicodedata). The standard lib is made from a combination of C and Python; depends on the module.
Some modules are also implemented both in C and Python; where the compiled version may not be available and the interpreter can then fall back on the Python one.
Or they have a core part that needs to be fast that's written in C, and more porcelain-like stuff in Python
does the python interpreter code rely on its standard library implemented in python?
Depends on what you mean by "the python interpreter code"
does the C code (interpreter) ever run python code to abstract a functionality that could have been written in C
Still not sure what you mean precisely.
The interpreter obviously runs Python code, and all of that code could have theoretically been written in C.
Yes, lots, mostly stuff is written in C for performance. Maybe Rust in the future too.
at least in the standard library
there’s also a policy that standard library modules with accelerators should have Python implementations too
ok so the C code parses python code, and I wanted to know if the C code sometimes executes python code, NOT PARSE IT, to facilitate the implementation
As I've written a similar message above, I've been wondering.. why is that the case? When would those modules not have been compiled, but the rest of CPython would have been?
(Of course it helps PyPy, but other than that I mean)
I think it’s mostly a semi-theoretical PyPy use-case
or other Python implementations
since CPython is the reference implementation
Okay, that makes sense.
So within CPython itself one wouldn't really run into needing the Python implementations
the interpreter core is all C, but it’s pretty easy to construct situations where a C call can trigger the execution of arbitrary Python code
I think that happens when generating the text for a syntax error.
args = Py_BuildValue("(O(OiiNii))", errmsg,
tok->filename ? tok->filename : Py_None,
tok->lineno, col_offset, errtext,
tok->lineno, end_col_offset);
this build a python object so that higher level code can do the heavy lifting, right?
maybe, it could also be C code that expects a Python object
Yes. Everything related to importing, for instance
unicodedata is pure C
I mean, I was looking for an example where C code runs python code to do something on the interpreter itself
and not run python code that has no effect on the interpreter
If you run 'python' the new repl is Python (_pyrepl).
I'm not sure what you mean by "effect on the interpreter", exactly. When you import a module, it can call hooks and codecs and so on that are written in Python, but importing a module does affect the interpreter, right?
the interpreter now has access to a new module, stored in sys.modules, which future imports can reuse
you can also “write” python code in C. That’s what the Cython compiler does. There isn’t a real distinction IMO.
it’s all the interpreter 🙂
there's also things like audit hooks, profiling functions, and trace functions that are called directly by the interpreter, and that can modify the execution of the code, but which are commonly written in Python
PDB is written in Python, for instance
ok, imagine it like this:
void interpreter_code() {
// Parse some python code like if, else, def, ...
// Do some C code
// Do some other code that is a pain in C but it is easier if it was implemented in python
// Idk, some setup to pass execution to a pre-written .py code
// Go back to c code
}
does this happen on the interpreter?
yes, all the time
a big part of the choice between writing stdlib modules in Python vs C is convenience
yeah, writing correct C is really hard
the threading module, for instance, is written in Python
I don’t think there are any builtins written in Python though
For example, encodings (pure python) is used during startup: https://github.com/python/cpython/blob/62a6e898e017c9878490544f6a227b8a187a949c/Python/codecs.c#L1684-L1686
Python/codecs.c lines 1684 to 1686
// Importing `encodings' will call back into this module to register codec
// search functions, so this is done after everything else is initialized.
PyObject *mod = PyImport_ImportModule("encodings");```
what
c and python?
not a good mix lol
yup
…sort of by definition, otherwise it wouldn’t be built in
NumPy is like this and it’s pretty great
hm. __import__ and __build_class__ are specifically designed to be overridden, though, typically by Python code
numpy’s array printing is all written in Python
oh, traceback printing in general is another good example of this tbh
no one wants to write fiddly string formatting code in C
there's no reason the interpreter couldn't print the traceback for an uncaught exception entirely from C code, but it does it from Python because it's easier and not performance sensitive
java java java java
lol
jk
everyone likes what they like
most professional devs regularly write code in many different languages. It's a "right tool for the right job" sort of thing
yup
one reason Python is so awesome and so successful is it’s very easy to write polyglot Python codebases where all the hot code is in a native language but all the boring high-level code is in Python
breakpoint() isn't written in Python, but does immediately delegate to Python code
hash() often will as well
If the builtins added by site count, then license and copyright are pure Python :P
help() is like breakpoint() - written in C, but immediately delegates to Python code
Something completely different: Did anyone here successfully add keybinds to the new REPL? I've looked at Lib/_pyrepl and there is a keymap.py and a default keymap, but I don't see when it would ever not be the default keymap/how you would load a different one...
(I'm asking for my <esc> + _ muscle memory)
the keymap switches implicitly in different contexts, but I don't believe there's a way for you to map your own custom keys
I don't think there's any philosophical opposition to making that configurable, so much as that there weren't enough people asking for it to justify the effort of making it configurable
all of that said, you might have an easier time upstreaming support for meta+_ if you want it
👍
other examples are that some math operations on huge integers are implemented partly in Python (_pylong.py) and some of the machinery for native generics and type statements (PEP 695) delegates to Python code in typing.py
it might be instructive to try running strace -e open,openat python -c "" 2>&1 | grep '\.py' to see all of the Python files that get loaded just for starting the interpreter
at least encodings and linecache
and a bunch more if you try something that raises an exception, with -c "1/0" for instance
alex@Null:/mnt/c/Users/alexr/Desktop/cpython$ python3
Python 3.12.12 (main, Dec 1 2025, 15:29:12) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
Traceback (most recent call last):
File "/home/alex/.vscode-server/data/User/workspaceStorage/c01f34bc25e03d5fd37f9a8abd6f0bf1/ms-python.python/pythonrc.py", line 5, in <module>
import readline
ModuleNotFoundError: No module named 'readline'
>>>
Readline also
that's down to whatever ms-python.python/pythonrc.py is doing. That's not the interpreter, that's VS Code
at least, I'm pretty sure
you are right, if I type on a normal cmd
alex@Null:~$ python3
Python 3.12.12 (main, Dec 1 2025, 15:29:12) [GCC 13.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
oh, or maybe not VS Code, maybe that's the MS App Store version of Python. One or the other
im using wsl
yes, but WSL could still be running the app store version of the interpreter, depending on your PATH
my discord unfortunately displays this properly (as in flagless)
can we have a pep for april 1st that changes def to fun?
Windows simply does not bundle any flags in its default fonts :D
@teal grotto ill share it here
Cool
...why is math.pow so much faster?
it is a well written algorithm
x * x is this
0 LOAD_FAST 0 (x)
2 LOAD_FAST 0 (x)
4 BINARY_MULTIPLY
6 RETURN_VALUE
pow is this
0 LOAD_GLOBAL 0 (math)
2 LOAD_ATTR 1 (pow)
4 LOAD_FAST 0 (x)
6 LOAD_CONST 1 (2)
8 CALL_FUNCTION 2
10 RETURN_VALUE
😕
exponental x ** 2 is this
0 LOAD_FAST 0 (x)
2 LOAD_CONST 1 (2)
4 BINARY_POWER
6 RETURN_VALUE
So why doesn't the standard ** operation use that algorithm?
hmm.. thought the embed would be typed out
long_mul(PyLongObject *a, PyLongObject *b)
{
PyLongObject *z;
CHECK_BINOP(a, b);
/* fast path for single-digit multiplication */
if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);
return PyLong_FromLongLong((long long)v);
}
z = k_mul(a, b);
/* Negate if exactly one of the inputs is negative. */
if (((Py_SIZE(a) ^ Py_SIZE(b)) < 0) && z) {
_PyLong_Negate(&z);
if (z == NULL)
return NULL;
}
return (PyObject *)z;
}
it is a design choice from the data model
For small numbers, this uses binary multiplication. For larger values, the function uses Karatsuba multiplication, which is a fast multiplication algorithm for larger numbers.
binary multiplication is quite fast in other words
the binary power part is very long, and far to long for me to understand, or even talk about
though i cannot comment on how the C part works, it is an algorithm that is very well written.
and the task of you, is to know when to use wich tool, and that part is what we control on the python side
the parts i have shown and qouted is from this very very excellent blog post
and it is an excellent read
Unfortunate that it doesn't go into why math.pow is faster though. I suspect it's because ** computes an exact integer result and math.pow does float operations.
math is float focused, so that does make sense
Would be interesting to see performance comparisons between doing ** on floats and math.pow
maybe we should just do it
my contribution ```In [23]: %timeit 2.0 ** 50.0
2.41 ns ± 0.0633 ns per loop (mean ± std. dev. of 7 runs, 100,000,000 loops each)
In [24]: %timeit math.pow(2, 50)
33.9 ns ± 0.144 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
20.2 ns ± 0.298 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
I guess you pay for converting from int to float
Objects/longobject.c lines 80 to 84
/* For int multiplication, use the O(N**2) school algorithm unless
* both operands contain more than KARATSUBA_CUTOFF digits (this
* being an internal Python int digit, in base BASE).
*/
#define KARATSUBA_CUTOFF 70```
Karatsuba multiplication is something i know off, but its been decades since i actually looked into the details
another surprising thing: if the numbers get above a certain size, the C code calls _pylong.py to do things like str->int conversion.
Can someone pls check with me this:
I believe that if there is a new line(\n) anywhere before tok->cur and tok->cur != \n then this code will have wrong col_offset and end_col_offset for the syntax error.
The code calculates the col/end_col offsets based on tok->line_start to tok->curr :
errtext = PyUnicode_DecodeUTF8(tok->line_start, tok->cur - tok->line_start,
"replace");
...
if (col_offset == -1) {
col_offset = (int)PyUnicode_GET_LENGTH(errtext);
}
if (end_col_offset == -1) {
end_col_offset = col_offset;
}
but then if that string doesn't end on a \n the code grabs the string until it finds the first \n.
Py_ssize_t line_len = strcspn(tok->line_start, "\n");
if (line_len != tok->cur - tok->line_start) {
Py_DECREF(errtext);
errtext = PyUnicode_DecodeUTF8(tok->line_start, line_len,
"replace");
}
If that \n is after tok->cur there is no problem but if the first \n is before tok->cur, python will call the syntax error with col_offsets outside the string
static int
_syntaxerror_range(struct tok_state *tok, const char *format,
int col_offset, int end_col_offset,
va_list vargs)
{
// In release builds, we don't want to overwrite a previous error, but in debug builds we
// want to fail if we are not doing it so we can fix it.
assert(tok->done != E_ERROR);
if (tok->done == E_ERROR) {
return ERRORTOKEN;
}
PyObject *errmsg, *errtext, *args;
errmsg = PyUnicode_FromFormatV(format, vargs);
if (!errmsg) {
goto error;
}
errtext = PyUnicode_DecodeUTF8(tok->line_start, tok->cur - tok->line_start,
"replace");
if (!errtext) {
goto error;
}
if (col_offset == -1) {
col_offset = (int)PyUnicode_GET_LENGTH(errtext);
}
if (end_col_offset == -1) {
end_col_offset = col_offset;
}
Py_ssize_t line_len = strcspn(tok->line_start, "\n");
if (line_len != tok->cur - tok->line_start) {
Py_DECREF(errtext);
errtext = PyUnicode_DecodeUTF8(tok->line_start, line_len,
"replace");
}
if (!errtext) {
goto error;
}
args = Py_BuildValue("(O(OiiNii))", errmsg,
tok->filename ? tok->filename : Py_None,
tok->lineno, col_offset, errtext,
tok->lineno, end_col_offset);
if (args) {
PyErr_SetObject(PyExc_SyntaxError, args);
Py_DECREF(args);
}
error:
Py_XDECREF(errmsg);
tok->done = E_ERROR;
return ERRORTOKEN;
}
Oh hey, this
is another example of what you were asking about yesterday @tidal sorrel
If you think it's wrong for some specific input, can't you just compile() that input and check?
I dont know how to do that. 😭
I dont even know what that will do
!e ```py
try:
compile("print(1/)", "foo.py", "exec")
except SyntaxError as e:
print(f"{e.lineno=} {e.end_lineno=} {e.offset=} {e.end_offset=}")
:white_check_mark: Your 3.14 eval job has completed with return code 0.
e.lineno=1 e.end_lineno=1 e.offset=9 e.end_offset=10
note that all of these indexes are 1 based, not 0 based, so that's pointing to the )
is this related to the issue I presented?
unless I misunderstood something, yeah? Aren't you saying that you think for certain inputs it gets end_offset wrong?
I'm showing you how to check end_offset
yes, when the error code is:
tok->cur
V
tok->line_start ->" This is a test \n Error Code \n
I believe that this will cause some errors
but I dont know the internals enough to know if there is any python code that would generate something like this
the "\n" are not escaped the are literally new lines
ah, so your question isn't "is the end column wrong for this Python input", it's "is there any Python input that results in this tokenizer state"?
yes, and I belive this tokenizer state producesa broken syntax error
based on this:#internals-and-peps message
I got an idea at least syntactically how dispatch table may look like if python ever support it natively. *Disclaimer: I'm not familiar with Python internals. *
But I imagine it might be implemented with function pointers , switch-case statements, and careful label-goto jumps in C.
If Python ever supports interpreting a C subset/assembly-like language, this might almost be it except it should NOT support any type of pointers, i.e. func pointers, and it should support only a tiny subset of Ctypes, definitely no function pointers, struct, etc... I know there're many smart people here so I'll leave it language designers.
Motivation:
#1 ease of writing more flexible and modular code
#2 data-code morphism and its respective representation,#3 interface for lower-level language development (C/ASM), Make CPython development more accessible, make building customizable small languages/runtime environment simpler.
Idea:
I'll follow Dennis Ritchie's pre-struct C complier (~Unix V4) data structures/ coding style, surprise it's quite pythonic. If we get smart we might even get inspired to port/ improve some data-structures into the standard Python library.
I'll provide a proposed specification, give me time.
The idea is having 2 representations right? so data and control structure.
I suggest the name Disp short for dispatcher/ dispatch table
the idea is for people to patch up/ prototype large sections of code,
sort of like a declaration in C.
One of the easiest way at least doing it pythonic I imagine is using dictionaries and lambdas.
Lambda Dictionaries
A key-value pair might look like this i.e. x: x+1. but you see the possibility here right? x is both the label and the entry-point to some expression, but it's a label under a declared namespace. Now see Disp is basically a list of key-value pairs, a dictionary.
Its behavior, such as iterable must be implementation-dependent and possibly support conditional overloading.
I purpose it should have some default behavior like stepping forward, but behavior can be changed by some sequence-like data-structure, like a list.
'x': 0
'y': 1
}
same as a dataclass that initialize x=0, y=1 for every instance
but x and y are instance variables that can be overloaded
pair.x = 2
# overloading pair = {'x': 2, 'y': 1}
not sure if this is a good idea for safety and implementation reasons
what if we allow this too?
new_pair = pair(y = 3)
# new_pair = {'x':2, 'y':3}
but we got to lookout for potential undefined behavior when designing these features.
The goal is to design a flexible update/ overloading mechanism
without overcomplications and become a potential security issue, and
balancing that with execution time and efficiency
Ordinals
the idea of this design is to offer flexible execution of code, treating data and code as the same.
but we might want to support order manipulation
new_initializer = initializer(y, x) ```
This is basically the data-structure side of it.
There is a control structure of it which uses **match-case statements** to access or overloaded the dispatcher, which I'll go through later.
In this specific scenerio, I thought why not make labels polymorphic, so to morphing between lambda statement and string only with the support ofDispatcher object. so we can express it like a list of lambda expression as such:
x : pass
y : pass
} ```
It initalized a `{x: None, y: None }` dispatcher.
## Namespace Goto amd Labels
This motivates the abstraction of control structures in a **data representation** format.
Here I'll demonstrate by constructing a forever wrapper.
```forever = {
entry : pass
body: pass
exit: goto entry
}```
Here It is like a declaration but for control structure, We can rip the benefits of C's goto. Yet we make it scoped, removing the one of its unsafe behavior. This is also one step closer for bridging C and Python. As they might just match nicely with **switch-case statements** in C.
It is also easy to see how forever is a wrapper by overloading the *body* of forever.
I'm not sure dispatch tables would help at the level of Python, they work in runtimes written in C or C++ for instance because in those low level languages you can somewhat (But even then not fully) control what it compiles into and minimize the dispatcher into an optimal sequences of instructions that the native instruction set supports, but in Python code the overhead of executing the Python program itself completely swallows up any advantage the dispatch table may have granted
I'm also somewhat certain that goto of any form is something that the Python team is not likely to add to Python, judging from discussions that I recall in the past, so it'd be very hard to convince anyone to add a feature involving goto
of course, we cannot gloss over the benefits of dispatch tables. Here's one mapping tokens with operators/ functions.
delimiter:
';' : splitline()
binary_operator:
'+': join()
':' : namespace()
',' : split()
}```
Are you using AI to write these messages?
Reason being goto is a mess in C. because it is too unhinged. But if we contain it, it could work
It sounded like AI because I have worked with it before, but the idea is mostly mine. I am currently have not using any AI for this proposal
I took some inspiration from AI, yes. But only ideas that I think might benefit
it shows
It's still going to be a very difficult task to convince the Python team of this, regardless of what I think. My views on goto are neutral and I'm not pro or anti goto when it comes to languages, but that doesn't matter because I'm not a Python language architect (Or whatever the people who design the Python language are called)
It's tough not to be influence by it, it's everywhere, AI
But still, the main problem of performance (Which is what I'm assuming is the main reason for this, in interpreter dispatch) being negated by the Python runtime not being able to take advantage of it like C or C++ can still remains
That does make me wonder what the most ideal way to write interpreter dispatch in a high level language like Python would be, that's an interesting thought
So I'm proposing using function pointer and dispatch tables in C/C++ for reference implementation
data- code morphism, like in LISP or its descendant
the idea basically is use a dict-like data-structure for control-structure prototyping, and use sequences to define the order of execution
does that make any sense?
a physical analogy would be a breadboard. overloading will be like changing the wiring, or resistance in a circuit
I think high-level language like python can have the flexibility to handle multiple representations, even graphical representation if you think networks. The power of a high level language is the immediacy of experimenting with new ideas, plug and play as we go
My idea is try to make Python support interpreting a subset of C, not exactly. But Python has match-case statement which if written carefully can easily map to switch-case statements. Maybe we can create a specific data-structure that address this, Dispatch tables. The idea probably floats around already in f-strings, but the connection is unclear. But what I see is they are two sides of the same coin, or at least very similar, almost ready for polymorphic code
ah no actually is more like a dict with a key which has an any type but exhibits poloymorphic behavior depending on its type, and value is a string. What you get is a generator. so [Any, Str]
so list of generators, and a mapping? I don’t know yet
what is the "OP" token used for?
used for operators
why is a OP token emited for a $ if its not valid syntax
It's really a catch-all for all punctuation
I wonder if there's any chance we can put interpreter frames on the native stack directly, I doubt it though considering what I'm seeing in the code
Also I think the compiler uses the same frame location and layout as the interpreters, so that's an additional thing to think about
The cost of that would be that deep recursion is more likely to lead to a segfault, right?
Interpreter frames were 1-to-1 with stack frames until 3.11 - allowing multiple Python frames to be evaluated by the same call to the eval loop is an optimization
It's even called out specifically as an optimization in the changelog - https://docs.python.org/3/whatsnew/3.11.html#faster-runtime
I don't see why that would be the case, I thought deep recursion leading to stack overflow errors in early Python versions was because _PyEval_EvalFrameDefault was itself recursive in those versions, not because the frames went on the native stack
if you put more frames on the native stack you are more likely to overflow the native stack, right?
Are you suggesting that it should use alloca() or something?
Sure it does take more stack space but it shouldn't cause overflows on, say, 1000 frames (I think that was the old stack size limit of Python, not sure about now)
Well I am aware that it's unlikely that my ideas will be accepted into the actual runtime but I've become a little obsessed with Python performance as of late, so I studied other runtimes and this is something they all seem to do
I'm not asking you to justify your proposal, I'm asking you to explain what it is that you're proposing. I'm still not sure I understand it
Is it using alloca() to grow a stack frame after it's created? Or space pre-reserved in every _PyEval_EvalFrameDefault to evaluate N Python frames before needing to recurse? Or something else?
It's not really a proposal admittedly, just a bunch of crazy ideas floating around in my head, basically this one is just pushing the frames to the native stack whenever an instruction that pushes a Python frame is encountered, instead of the stack in the thread state (I think that's where it currently goes). What makes this likely dead on arrival is that you can only do this by modifying the frame and stack pointer registers, which means dropping to a lower level than C
sounds like alloca(), or something almost exactly like it
and the big objection to alloca() is that it's unsafe, and provides no protection against stack overflow. But maybe in Python's case that's fine, since there's already the recursion limit
instead of the stack in the thread state (I think that's where it currently goes)
Off the top of my head, I believe the first Python frame evaluated by each call to_PyEval_EvalFrameDefault(the "entry frame") is stored on the C stack, and all other Python frames are stored on the heap
it indeed might be faster to use alloca, and it does seem to be portable enough - Linux, macOS, and Windows all seem to support it. It might be an interesting experiment
I did come up with an elaborate idea to write bytecode definitions in Python (As opposed to bytecodes.c that is currently done) and turn them into LLVM bitcode and link the resulting object file into the runtime in my fork, I guess I'll let everyone know how that pans out if anything interesting happens
I doubt it'll be well received, you do have to rewrite a lot of the runtime to do that
hm? really? I haven't looked closely at the eval loop, but I'd expect it to be a pretty surgical change
all you're changing is one heap allocation to a stack allocation, afaics
Hmm, what about the current infrastructure in place that stores the frames on the heap though?
I guess maybe that could be kept and you could only have this for the tier 2 interpreter or something
Don't be so down on yourself! Ideas are welcome, especially things others haven't thought of yet. You should expect people to ask critical questions though
you just don't call it
Haha thanks, it's just that the ideas are a little weird, and much of it is stuff I learnt from studying other runtimes. I guess I'll try them out first and come back here to share the results if anything comes of it :)
I was about to agree but I caught sight of some documentation that says it may not be possible
https://github.com/python/cpython/blob/86b8617747699416fcf9cd4ce6ea1da58a66f70e/InternalDocs/frames.md?plain=1#L18
InternalDocs/frames.md?plain=1 line 18
Python semantics allows frames to outlive the activation, so they need to```
Yeah for example sys._getframe reifies a frame object, and so do generators. So they must be heap allocated.
but the Python frame object is lazily created already. I thought we're talking about the interpreter frame, not the Python frame object
Yes I meant the interpreter frame, but I don't know how generators work
I am not sure whether generators take a _PyInterpreterFrame from the heap or not
Yes but it still applies to generators and async stuff which copy over the interpreter frame into their own heap space
yes, but those need to copy already, right?
my point is that we already have some interpreter frames on the stack, so if things work today, I don't see how this could stop us from putting all interpreter frames on the stack. That's how things were pre-3.11 anyway
Hmm I don't recall exactly now, but i think if they copy youre right that it would be possible
Wait I thought pre 3.11 had heap frames too and it was just the eval function that was recursive
I was not around during the 3.11 time so I don't know much about it back then
yes its basically this
pre-3.11 didn't have "interpreter frames", the C state for each Python frame was stored directly on the _PyEval_EvalFrameDefault call's stack
I think that's wrong, we had full PyFrameObjects
TheShermanTanker is right in that the only reason we used the C stack is that the eval function was recursive
but it is true that 3.11 didn't have "interpreter frames", they're a new thing added from 3.11 onwards
I'm 100% certain that entry frames are already on the stack
Yeah that's right
the one piece I'm not totally positive about is when the interpreter inserts shim frames - is it the case that the entry frames don't run any actual user code? If so, then you might be right as well, that the generator and async stuff only works because the entry frame can't be a generator or coroutine...
yeah.
InternalDocs/frames.md?plain=1 lines 104 to 111
### Shim frames
On entry to `_PyEval_EvalFrameDefault()` a shim `_PyInterpreterFrame` is pushed.
This frame is stored on the C stack, and popped when `_PyEval_EvalFrameDefault()`
returns. This extra frame is inserted so that `RETURN_VALUE`, `YIELD_VALUE`, and
`RETURN_GENERATOR` do not need to check whether the current frame is the entry frame.
The shim frame points to a special code object containing the `INTERPRETER_EXIT`
instruction which cleans up the shim frame and returns.```
ok, so then yeah, there is an extra piece that would be missing: stuff would need to get copied from the stack to the heap at those points if every interpreter frame were on the stack
I'm not sure why we use the shim term here, considering if you grep for shim in the interpreter source code, it's actually referring to a dummy frame that is used for dunder init
that's a little confusing
"trampoline" would probably be a better word, but I'm sure that's overloaded thanks to ctypes...
there's only so many good words 😆
There's trampoline in the perf jit and the normal jit too
There's more than 1 compiler?
ok. well, this is not as easy as I imagined, but probably still not very hard, as far as changes to the eval loop go. It might be worth the experiment. I'm by no means an expert, but it seems like the pieces would be:
- when allocating a new interpreter frame from the eval loop, use alloca()
- add a new frame->owner flag to represent alloca() frames
- update the instructions that need the interpreter frame to be heap allocated to check that flag and copy the interpreter frame onto the heap as needed
- profit?
I think there's another snag that I just realized
it's basically the same optimization that we already do for Python frame objects, lazily heap allocating them on demand, just applied to the interpreter frames themselves
No but to get the perf stuff working for Python frames, it has to emit binary information dynamically (DWARF), which makes it technically a JIT :), albeit a very simple one
How are we going to drop the frames once the scope ends? I don't think alloca allocated memory can be freed manually
Ah haha
DWARF debug info is itself a bytecode language describing a stack machine
I'm going to continue trying ideas out to see what sticks in the meantime :)
hm? You wouldn't, you'd reuse them
you're using alloca() to make an array of interpreter frames on the C stack. You'd grow it if you need to push a new interpreter frame and the array is full. Otherwise you'd just write to the Nth slot of the array.
you'd clear the interpreter frame when you pop out of the Python function, but it wouldn't get deallocated, the space would still be sitting there. You'd track the Python call depth, and that gives you the array index. And you'd track the array size, to know when the Python call is deeper than the array size to know when to alloca() some more space
though I guess there is a new failure mode implied there. If you push 500 Python calls and then pop them all, you haven't hit the recursion limit, but you have allocated space on the stack for 500 interpreter frames. If you then call into a C function, as opposed to a Python function, that space doesn't get reused. If that C function calls back into Python, you can repeat the above to allocate stack space for 500 more interpreter frames. Repeat that dance enough times and you'll stack overflow even without hitting the Python recursion limit
You could fix that by decrementing the stack pointer to shrink the array's capacity down to its size before calling the C function, but then you are indeed back to architecture dependent assembly instead of just alloca()
i wrote a little bit about relevant cpython history here: https://hauntsaninja.github.io/cpython_recursion_crash.html
Correct me if im wrong, but the tokenizer(tok_nextc()) reads a "fake" new line when it finds EOF, right?
I believe that is called the implicite new line
ok, after some tests, it is true, there is a "fake" just before EOF
can someone explain me why:
verify_end_of_number allows non-ASCII characters to valid number endings?
static int
verify_end_of_number(struct tok_state *tok, int c, const char *kind) {
...
if (c < 128 && is_potential_identifier_char(c)) {
tok_backup(tok, c);
_PyTokenizer_syntaxerror(tok, "invalid %s literal", kind);
return 0;
}
return 1;
}
where does python then check for the invalid syntax
:x: Your 3.14 eval job has completed with return code 1.
001 | File [35m"/home/main.py"[0m, line [35m1[0m
002 | [1;31m1[0mx
003 | [1;31m^[0m
004 | [1;35mSyntaxError[0m: [35minvalid decimal literal[0m
!e ```py
1Ã
:x: Your 3.14 eval job has completed with return code 1.
001 | File [35m"/home/main.py"[0m, line [35m1[0m
002 | 1[1;31mÃ[0m
003 | [1;31m^[0m
004 | [1;35mSyntaxError[0m: [35minvalid syntax[0m
I'd think we'd want the same "invalid decimal literal" error in both of those cases, with the caret pointing to the number in both
where does python then check for the invalid syntax
It's winding up tokenizing it as a valid decimal literal, and then the parser fails because a decimal literal followed by a name is a syntax error. It's giving basically the same error for1Ãas it would give for1 Ã- which is not the same error as it gives for1A
it doesn't seem like unicode digits are considered valid so the ascii check seems to be quite pointless
I think this is only firing after it has finished tokenizing the digits. it's checking the next character after the last digit character in the literal
why does the tokenizer read raw bytes from the source code instead of unicode codepoints?
I don't really know anything about that, but I imagine that allows the tokenizer to work even if the file is encoded as (for example) ASCII
ASCII is fully compatible with UTF-8, and UTF-8 is one encoding for Unicode - so an ASCII file does contain UTF-8 encoded Unicode codepoints
I'm guessing the answer might just be that tokenizing hasn't changed much since Python 2 (since back then source code never got decoded into Unicode codepoints)
One of the jobs of the tokenizer is to decode the potentially non-unicode source of a Python file. https://peps.python.org/pep-0263/
Ooh, interesting. I had assumed that coding comments would be handled before tokenization
But wait - are you saying it's the tokenizer itself that's decoding the source according to the coding comment as it goes along? I guess that would make sense, but it's totally not what I would have guessed
The coding line needs to be either the first or second line, and instructs the tokenizer how to decode the rest of the file to UTF-8, which is then tokenized
Hm, a coding comment specifies the encoding to use to decode the file. The given encoding is fetched from the encodings registry and fed the bytes of the file, which it decodes into Unicode. Are you saying that that Unicode is then re-encoded as UTF-8 to be fed into the tokenizer?
Parser/tokenizer/file_tokenizer.c lines 89 to 98
line = PyObject_CallNoArgs(tok->decoding_readline);
if (line == NULL) {
_PyTokenizer_error_ret(tok);
goto error;
}
}
else {
tok->decoding_buffer = NULL;
}
buf = PyUnicode_AsUTF8AndSize(line, &buflen);```
decoding_readline returns a str, and PyUnicode_AsUTF8AndSize converts it to UTF-8 before tokenizing it. This is hit each time the tokenizer needs a new line. weird 😆
Greetings CPython Core Devs,
I want to know what .args is actually about in the built-in Exception class? Why do we have it?
Is it necessary to populate .args?
-
is
.argsthe standard for showing exception message? Or having a__str__()on any sub-exception class is okay and recommendable too? -
does not populating
.argscan have side effects? If any, does having a__str__()on any sub exception class is enough for exception message? -
Should all nested subclasses of Exception (e.g.:
Buzz(Foo) -> Foo(Bar) -> Bar(Exception)) must call superclass with a message so that it reaches the built-in Exception'sargsattribute ? -
Is an exception message supposed to come from
.args(or__str__()is recommended too over.args) ?
What's the modern best practice in this case?
Let me know if this isn't the correct channel.
Is it necessary to populate
.args?
Technically, no - at least not on CPython. It looks like it's set by__new__rather than by__init__, so it gets set even if your subclass overrides__init__and doesn't delegate to the superclass's__init__.
Ooh