#internals-and-peps

1 messages · Page 35 of 1

boreal umbra
#
In [1]: stuff = {'a': 1, 'b': 2}

In [2]: v1 = stuff.items()

In [3]: v2 = stuff.items()

In [4]: v1 is v2
Out[4]: False

this surprises me--I'd expect them to be the same.

swift imp
boreal umbra
swift imp
#

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

boreal umbra
#

most iterable builtins return a separate iterator object for iteration

raven ridge
#

!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))

fallen slateBOT
hybrid relic
#

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

spark magnet
swift imp
grave jolt
#

ew... it only checks that the size didn't change during iteration

clear hill
#

every time the content changes, increment an atomic integer…

raven ridge
quick snow
fallen slateBOT
quick snow
#

Here I change the size of the hash table, but it still doesn't notice, because the len is identical at the end..

grave jolt
meager nacelle
#

That does seem nasty

boreal umbra
#
    @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'
grave jolt
feral island
#

easiest way to crash python

grave jolt
#

that might be ctypes.string_at(0)

feral island
#

needs an import, too much work

pliant tusk
hybrid relic
#

Seeing bytecode of a function being replaced as easily as one would reassign a variable was not on my 2026 bingo card

boreal umbra
#

oh it won't (because it's a set). hmm

meager nacelle
# boreal umbra the last two outputs surprise me ```ipython In [7]: n = 'hi' In [8]: def my_fun...

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
boreal umbra
clear hill
#

my name is on a PEP as an author now 🙂

#

nice little victory for the week

hybrid relic
#

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

clear hill
#

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

hybrid relic
hybrid relic
#

The second half of that statement I mean

#

Ah right, I guess this also means that what I was thinking about was feasible, nice

clear hill
#

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 🙁

hybrid relic
#

Wait whaaaaat

#

That's really lame :/

clear hill
#

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.

hybrid relic
#

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

clear hill
#

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”

meager nacelle
#

Oh the abi3t PEP, nice

hybrid relic
#

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)

clear hill
#

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

hybrid relic
#

That makes sense in my head but is also profoundly unfortunate

#

Can't just break all of that to be faster, I get that

clear hill
#

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

hybrid relic
#

That's good to hear

uneven raptor
#

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

crimson hatch
#

There are techniques to trace with FFI if I recall correctly, but it's incredibly difficult

uneven raptor
#

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

hybrid relic
#

The only HPy I know is the thing that PyPy has in their interpreter

clear hill
hybrid relic
#

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

deep dirge
#

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.

hybrid relic
#

Thanks though!

deep dirge
boreal umbra
#

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

grave jolt
#

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):
...

hybrid relic
#

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

swift imp
#

Pep827 looks thick

raven ridge
#

!pep 827

fallen slateBOT
steel solstice
#

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

grave jolt
#

It will be really cool if it's accepted

swift imp
#

I don't know enough to have commentary. I don't really understand a lot of it

neat delta
# fallen slate

i like how this completely ignores the fact that args and kwargs are not mandatory names

grave jolt
#

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

gilded flare
steel solstice
#

wdym I didn't see anything for it, the last I've heard on this was PEP 677

swift imp
grave jolt
#

that looks like a joke someone would make about how complicated python's type annotations became

swift imp
#

I still don't get why we have to wrap Literal["String"] and it cant just infer "String" as literal

swift imp
#

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 -> ?

swift imp
grave jolt
#

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

boreal umbra
#

didn't someone suggest set literals as an alternative?
Literal[420, 69] -> {420, 69}
though I like Literal as-is.

grave jolt
#

!cleanban @cursive bobcat free macbook scam

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied ban to @cursive bobcat permanently.

radiant garden
meager nacelle
#

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.

gilded flare
fallen slateBOT
#

Grammar/python.gram line 92

func_type[mod_ty]: '(' a=[type_expressions] ')' '->' b=expression NEWLINE* ENDMARKER { _PyAST_FunctionType(a, b, p->arena) }```
hybrid relic
#

Speaking of typing are we going to get Python where the types are used instead of just being there for code editors to analyze

meager nacelle
#

There already are tools that use them at runtime

swift imp
#

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?

gilded flare
#

highly doubt python will abandon being dynamically typed

#

but the developers' choices are a different story

deft shore
#

nice

spark magnet
swift imp
spark magnet
uneven raptor
gilded flare
#

oh

#

that makes sense

hybrid relic
tired hare
#

^

#

id imagine python would need a major refactor to change pythons typing system

uneven raptor
#

such as PEP 827?

#

oh you mean the stdlib

meager nacelle
#

There are things in the stdlib that make use of annotations for specific features, such as dataclasses with KW_ONLY, ClassVar and InitVar.

tired hare
grave jolt
#

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)

uneven raptor
#

me too, but it wouldn't surprise me if the stdlib were to become statically typed someday

meager nacelle
#

I could see type hints in some parts of the stdlib

grave jolt
#

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

tired hare
meager nacelle
#

There are also libraries like beartype that I believe will do things like this.

grave jolt
#

no, beartype inserts runtime checks

#

Static typing means that type consistency is checked before the program is run

meager nacelle
#

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.

grave jolt
#

mypyc is an interesting tool that I need to try out

gilded flare
grave jolt
#

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

hybrid relic
static hinge
flat gazelle
#

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.

hybrid relic
hybrid relic
grave jolt
#

I think GDScript has something similar

flat gazelle
swift imp
spark magnet
halcyon trail
#

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

primal prism
#

hello

clear hill
#

TIL KeyError works this way:

#
first line
second line
>>> print(str(KeyError("first line\nsecond line")))
'first line\nsecond line'```
boreal umbra
clear hill
#

Guido closed the issue I opened about it 🤣

boreal umbra
#

you got guido'ed! guido_intensifies
what did he say?

clear hill
boreal umbra
#

py_guido 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. py_guido
he has spoken

clear hill
#

the wisdom of the dutch

boreal umbra
#

it is now officially a rule on this server that any time guido is quoted, there must be py_guido on both sides

static hinge
#

¡!

gilded flare
merry venture
#

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

clear hill
#

you can subclass KeyError and define __repr__ too

quiet patio
#

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.

pearl river
quiet patio
#

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.

low moss
#

Hello guys ! I want to ask if this server is for learning scripting and coding

#

I want to learn some

hybrid relic
#

Yep, you can do that, just in other channels (This one is for discussing the internals of the Python runtime)

quiet crane
#

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?

boreal umbra
#

!cpban 1474812346684670094 crypto scam

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied ban to @stone temple until <t:1773152757:f> (4 days).

slate geyser
#

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

feral island
#

But the procedure would just have been to send a PR changing that file

meager nacelle
#

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.

meager nacelle
#

Answer: No, __annotate__ acts as a hidden descriptor, if you try to set it it sets __annotate_func__ instead

clear hill
meager nacelle
#

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
fallen slateBOT
glacial depot
#

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

winged orbit
#

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.

feral island
uneven raptor
#

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?

winged orbit
#

As like a perf thing probably? Interesting

uneven raptor
#

yeah. technically, the ref is moved as of 3.14 due to LOAD_FAST_BORROW; it's just released in a difference place.

winged orbit
feral island
#

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

winged orbit
uneven raptor
#

ah, it uses METH_FASTCALL, so it has a C array rather than a tuple, but it's the same concept

uneven raptor
#

basically:

  1. eval loop creates WarnsWhenDeleted instance
  2. finds a C function (operator.call), moves the WarnsWhenDeleted instance to an argument tuple (I think? there might be an extra incref/decref)
  3. calls the C function with that tuple
  4. operator.call executes x
  5. x deletes its local reference, but the arguments to operator.call still hold a reference
  6. operator.call finishes and releases the reference, deallocating the object
winged orbit
#

Hm, is there a reason why it doesn’t move the reference over? Generality to allow operator.call to call some naughty C function?

uneven raptor
#

operator.call doesn't know what it's calling ahead of time

winged orbit
#

I guess I’ll poke at the code and see if this all makes sense…

uneven raptor
#

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

feral island
#

the code might be pretty opaque here, the refcounting stuff is deep in the operator loop

winged orbit
#

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

uneven raptor
#

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

raven ridge
fallen slateBOT
#

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);
}```
raven ridge
#

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

winged orbit
hybrid relic
#

Sorry I am very immature

merry venture
#

anyway i'm here just for the idea of a hyperloop powered by hypercoroutines that are just async generators under the hood

glass mulch
fallen slateBOT
#

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```
glass mulch
feral island
#

sure looks like it

glass mulch
#

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?

radiant garden
#

a bug is a bug

spark magnet
#

even if it's only a distraction for the next time we look for problems, it's good to fix now.

glass mulch
#

Thanks, I'll open an issue and propose possible approaches for a PR.

meager nacelle
#

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.

steel solstice
#

should typing.casts params be pos only? I think it'd be a bit of a performance optimisation

merry bramble
#

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

grave jolt
merry bramble
#

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

grave jolt
merry bramble
#

but we wouldn't remove them at runtime without first emitting a runtime deprecation warning for several years as well

grave jolt
#

yeah that's true

merry bramble
#

(also, type checkers don't complain about usage of the deprecated PEP-585 items, even though PEP-585 says they should 😆)

grave jolt
merry bramble
#

Oh, I wasn't aware of the pyright option -- that's good

grave jolt
#

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

merry bramble
#

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

grave jolt
merry bramble
#

oh, nice

grave jolt
#

!pypi flake8-pep585

fallen slateBOT
#

flake8 plugin to enforce new-style type hints (PEP 585)

Released on <t:1677412919:D>.

grave jolt
#

there's also this flake8 plugin 🙂

merry bramble
grave jolt
#

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]
merry bramble
#

huh

#

flake8-pyi does that better than Ruff then

#

there must be 1,001 linters that try to catch this

grave jolt
#

so flake8-pep585 was low key a waste of time

merry bramble
#

flake8-pyi has only ever supported stub files, though

grave jolt
#

oh

merry bramble
#

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

crimson drum
round path
crimson drum
#

Who?

round path
# crimson drum 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.

crimson drum
#

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

boreal umbra
#

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")

boreal umbra
jovial flame
#

!clban 871340972653576233 spam bot

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied ban to @sharp comet permanently.

dusky sapphire
dusky sapphire
quick snow
dusky sapphire
#

Oh ok

alpine rose
#

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)

alpine rose
#

yes, scroll up to see the discussion

spare geyser
#

Wow

grave jolt
glass mulch
#

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:

  1. 3 reference leaks: add Py_DECREF(item) on failure paths in deque_append_impl (381), deque_appendleft_impl (428), deque_copy_impl (631)
  2. 3 default_factory use-after-free races: add critical section or atomic-load+incref in defdict_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.

Gist

cpython-review-toolkit report for _collections. GitHub Gist: instantly share code, notes, and snippets.

glass mulch
#

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;
round path
glass mulch
#

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?

stray falcon
#

does anyoen have a game idea? it can be 2D and 3D, ill be making it in python

#

anyone*

#

?

raven ridge
faint river
stray falcon
#

Mb

faint river
#

!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

fallen slateBOT
delicate bane
faint river
#

dang ok

static hinge
#

Json only supports string keys

grave jolt
#

!cleanban @warm canopy 7d stop spamming your article

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied ban to @warm canopy until <t:1774419452:f> (7 days).

faint river
#

which is seemingly unexpected behaviour

zenith topaz
spark magnet
clear hill
#

Seems like the major complaint was overhead in the happy case. Maybe there’s a clever zero-overhead way to add the check?

copper crescent
spark magnet
copper crescent
#

So if I loaded this json it would become a dict[str,dict[str,str]]

native flame
#

yeah, keys can only be strings in json

#

values can be numbers

spark verge
#

!e

import json
print(json.dumps({1:1}))
fallen slateBOT
spark verge
#

oh you've already run that today

raven ridge
#

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

fallen slateBOT
#

Include/internal/pycore_dict.h lines 171 to 175

typedef enum {
    DICT_KEYS_GENERAL = 0,
    DICT_KEYS_UNICODE = 1,
    DICT_KEYS_SPLIT = 2
} DictKeysKind;```
raven ridge
#

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

clear hill
#

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.

clear hill
#

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.

boreal umbra
#

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.

clear hill
#

No, no concrete evidence. I wouldn't want to deal with orjson as an upstream dependency though.

#

given their practices and communication style

raven ridge
#

I have some concrete evidence - which I could link to if they hadn't disabled issues.

boreal umbra
#

do you think they're preparing to mothball the project, or what?

clear hill
#

no, I think they just didn't like dealing with issues and PRs

#

oh hey orjson will support free-threaded Python in 3.15

safe basalt
rich cradle
#

<@&831776746206265384> maybe scam or at least spam ^

grave jolt
#

!cleanban @hollow owl giveaway scam

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied ban to @hollow owl permanently.

uneven raptor
#

...why does orjson have issues disabled?

molten onyx
#

There is no open issue tracker or pull requests due to signal-to-noise ratio.

boreal umbra
static hinge
mossy hearth
winged sphinx
static hinge
#

At least it wasn't a horizontal acquisition. Those usually happen to kill the competition.

spark magnet
#

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.

left gust
#

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

raven ridge
#

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.

clear hill
regal glen
#

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 🙏

sharp roost
#

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.

clear hill
#

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

sharp roost
#

Itanium C++ ABI enforces that first vtable will be at offset zero (and push PyObject_Head).

clear hill
#

that said, I doubt anyone has thought much about itanium support

#

a little surprised that’s important to you in 2026

sharp roost
#

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.

clear hill
#

oh maybe I’m misunderstanding what you’re saying

sharp roost
#

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).

clear hill
#

does nanobind or pybind11 handle this?

sharp roost
#

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.

raven ridge
#

CPython itself puts extra stuff before PyObject_HEAD sometimes - the gc head goes before the PyObject*

#

See PyGC_Head

sharp roost
sharp roost
clear hill
#

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

raven ridge
clear hill
#

I’m not aware of anyone doing what you’re trying to do, you may very well be the first person to try…

sharp roost
#

the core of this problem is to have virtual class that is both valid in c++ and Python

raven ridge
#

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

clear hill
#

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.

sharp roost
#

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.

sharp roost
#

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

raven ridge
#

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...

uneven raptor
#

it's not

#

I think it's fine here though because the object isn't GC tracked

sharp roost
#

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.

uneven raptor
#

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

sharp roost
#

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.

clear hill
#

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.

sharp roost
boreal umbra
#

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

boreal umbra
feral island
#

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

crimson hatch
whole jewel
#

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?

feral island
#

Feel free to open an issue on CPython to improve this

whole jewel
#

That would be my first cpython issue xD. did no one found this before?

clear hill
whole jewel
whole jewel
feral island
whole jewel
whole jewel
#

Oh cool. I did not see your message earlier. Is this commit targeted for python 3.15+?

quick snow
whole jewel
#

Couldn't find an already registered issue for that

#

Thanks Jelle & ngoldbaum for suggesting submitting an issue!

swift imp
whole jewel
#

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?

raven ridge
#

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

weak hawk
#

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

radiant garden
#

you can use it to customize import statements

#

for various esoteric and dubiously-exoteric purposes

feral island
#

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

glass mulch
#

__import__ was a nice way to break some attempts at sandboxing Python, so it's at least didactic 🙂

meager nacelle
#

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"))
fallen slateBOT
raven ridge
#

__import__("collections.abc") returns the collections module exactly because import collections.abc binds the collections name

static hinge
#

Importlib isn't about saving imports. It's about dynamic imports

hollow dawn
#

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"

primal cypress
#

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

hollow dawn
#

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 👍

hollow dawn
#

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?

hollow dawn
#

thanks again

primal cypress
hollow dawn
#

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:

  1. making sure the uops are in a form that is more optimizable, which involves modifying the bytecodes a bit to be composed of uops
  2. 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.
  3. work into _PyOptimizer_Optimize as optimizing a somewhat traditional IR
hollow dawn
#

does python have a 3 layer IR system?

  1. "instructions" as part of an "instruction sequence"
  2. bytecodes
  3. 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__?)

primal cypress
# hollow dawn hmm, so the normal interpreter runs, except has counts for `jump_back` or `resum...

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

primal cypress
#

-# To my knowledge anyways *

round path
round path
meager nacelle
meager nacelle
#

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

hollow dawn
hollow dawn
hollow dawn
primal cypress
# hollow dawn thanks, is it that the specializing adaptive interpreter itself is the one that ...

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.

copper crescent
#

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'?

weak hawk
#

Since it must scan through all other keys to find close matches, however we define that

raven ridge
#

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

weak hawk
#

Oh, good point

raven ridge
#

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))

fallen slateBOT
raven ridge
#

that's just weird.

#

but I imagine there's backwards compatibility reasons why it must remain that way

faint river
#

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)

round path
hybrid relic
#

Nevermind kenjin said it before me, didn't see the explanation since discord started me on kevin's message

quick snow
#

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).

round path
hollow dawn
#

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?

uneven raptor
#

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

round path
hybrid relic
#

Predefined assembly templates inserted into executable memory based on the current operation

hybrid relic
round path
hybrid relic
#

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

uneven raptor
round path
#

It's not that it goes straight from bytecode -> uop -> machine code, there's actually optimization passes in the middle.

hollow dawn
#

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

hybrid relic
#

That's a better description of it than mine yeah

daring pendant
#

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

GitHub

Previously, http.client would always send content body immediately and ignore any 100 responses. This change makes HTTPClient.request() wait for a Continue response if the Expect: 100-Continue head...

hollow dawn
#

now that's the oldest issue I've ever seen

quick snow
#

!warn 1260254962789777470 Do not advertise anything here, including telegram channels.

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied warning to @native hawk.

deep dirge
daring pendant
#

Any tips on how I can get people to take a look at it?

boreal umbra
#

ZeroIntensity, who frequents this channel, apparently looked at that PR in the past.

uneven raptor
#

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

feral island
daring pendant
feral island
#

Maybe but it also adds a new parameter. I feel like implementing more of HTTP naturally leads to more code to maintain

faint river
#

<@&831776746206265384> scam ^ (now deleted message, Jelle isn't scamming)

raven ridge
#

!pban 1486595953531027456 scan

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied ban to @arctic totem permanently.

boreal umbra
fallen slateBOT
#

:ok_hand: Added jelle’s-typing-scam to the names list.

spark verge
#

I worry about PEP 789 I think it's too niche and too difficult to use

boreal umbra
#

!pep 789

fallen slateBOT
spark verge
#

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

static hinge
#

the inner workings of task cancellation is forbidden grey beard knowledge

wanton flame
# fallen slate

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.

clear hill
#

my EuroPython talk on the CPython ABI was accepted 😎

#

just bought tickets for Poland 🙂

daring pendant
hollow dawn
wanton flame
# wanton flame Pretty sure that won't make 3.14! Seems unlikely for 3.15 too (40 days to featur...

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.

spark verge
#

Ah found it

#

Looks slightly outdated

wanton flame
#

it'll always be outdated!

spark verge
#

Nuh uh, eventually there will be a CI job that automatically updates the policy

clear hill
#

only dutch AIs allowed

crisp locust
#

I use Claude Code always supervised by me

spark verge
#

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

fallen slateBOT
hollow dawn
spark verge
hybrid relic
# round path I don't really understand your point? The IR is optimized and emitted using a da...

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

hollow dawn
#

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

hybrid relic
hybrid relic
round path
# hybrid relic Sorry I didn't do a good job explaining it, my main concern is that the current ...

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

GitHub

Experimental proof-of-concept, currently only working on x86-64 Linux. It is not intended for merging or detailed review, but rather to demonstrate the direction CPython's JIT could take an...

hybrid relic
#

Good lord!

#

All the person did was change the backend to DynASM and nothing else? That is incredibly impressive

hollow dawn
#

that is super cool

round path
#

It's claude assisted I think, otherwise I think this would take a lot more effort

hybrid relic
#

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?

hollow dawn
#

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

round path
#

FWIW, I have looked at Dmitry's IR for PHP even before it got upstreamed

round path
#

PHP has both method and tracing and I was informed the tracing one performed better for them last I contacted them

hybrid relic
#

That said, yeah LLVM compiles far too slowly for its JIT to be used by pretty much anyone

hollow dawn
#

I remember some post or something where moving to a method jit was brought up as a worst case scenario

hybrid relic
#

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

round path
hybrid relic
#

Ah alright that makes sense

hybrid relic
hollow dawn
#

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

hybrid relic
#

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

round path
hollow dawn
#

since I didn't look too closely into it, I'm almost certain you're more right than me

hybrid relic
#

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

round path
#

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

hybrid relic
#

That's cool

round path
hybrid relic
#

I guess it's for good reason but it's kinda sad

hybrid relic
#

There are reports out there that once its compiler is shut down for benchmarking purposes it actually does worse than cpython (Both purely interpreted)

round path
hybrid relic
#

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

round path
#

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.

primal cypress
#

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...

round path
#

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

hybrid relic
hybrid relic
round path
primal cypress
hybrid relic
#

I think we could bypass even the stack

round path
hybrid relic
#

Oh that's new to me, oops

#

Is that a recent addition?

primal cypress
#

it was a few months back

hybrid relic
#

Sweet

primal cypress
#

but idt it was in 3.14

round path
hybrid relic
#

Is it only the top of stack that enjoys this benefit? Ah alright I'll give that a read

round path
#

and del can run arbitrary python code

#

in pypy this isnt a problem cos it doesnt use refcounting

hybrid relic
#

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

hollow dawn
hybrid relic
#

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

hybrid relic
#

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

round path
# hybrid relic https://godbolt.org/z/9nzjYzE39

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.

hybrid relic
#

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

round path
# hybrid relic Don't they both yield very similar dispatch instruction sequences? indirectbr wa...

I found it's not the dispatch, it's:

  1. 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.
  2. 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.
hybrid relic
#

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

round path
#

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.

hybrid relic
#

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

hybrid relic
#

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

round path
#

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.

round path
hybrid relic
#

That's good, hopefully they work out well

round path
#

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 🙂

hybrid relic
#

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

round path
hybrid relic
#

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)

round path
#

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

hybrid relic
#

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

round path
#

Which FT bug? if you mean FT support for the JIT, we haven't landed that yet 🙁

round path
hybrid relic
#

Yeap I was referring to that :(

tidal sorrel
#

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.

spark magnet
gilded flare
tidal sorrel
gilded flare
tidal sorrel
crimson hatch
fallen slateBOT
#

Parser/tokenizer/helpers.c line 61

tok->done = E_ERROR;```
tidal sorrel
#

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
tidal sorrel
#

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

hybrid relic
#

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

hybrid relic
#

I wonder what could've been if the answer to the question he asked at 31:04 had been yes all those years ago

tidal sorrel
#

why is this done when tokenizing?

    /* Peek ahead at the next character */
    c = tok_nextc(tok);
    tok_backup(tok, c);
gilded flare
fallen slateBOT
#

Parser/lexer/lexer.c lines 636 to 638

/* Peek ahead at the next character */
c = tok_nextc(tok);
tok_backup(tok, c);```
raven ridge
#

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

tidal sorrel
tidal sorrel
#

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

quick snow
#

yeah, I think it just counts codepoints

#

or maybe even bytes

deep dirge
#

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.

GitHub

GitHub is where people build software. More than 150 million people use GitHub to discover, fork, and contribute to over 420 million projects.

Python documentation

This module provides access to the Unicode Character Database (UCD) which defines character properties for all Unicode characters. The data contained in this database is compiled from the UCD versi...

grave jolt
#

omg that's huge

#

(the grapheme iteration)

clear hill
#

it won't fix this though:

#
'🇦🇺'```
boreal umbra
clear hill
#

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

tidal sorrel
boreal umbra
quick snow
#

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

tidal sorrel
quick snow
tidal sorrel
quick snow
#

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.

clear hill
#

at least in the standard library

#

there’s also a policy that standard library modules with accelerators should have Python implementations too

tidal sorrel
quick snow
#

(Of course it helps PyPy, but other than that I mean)

clear hill
#

or other Python implementations

#

since CPython is the reference implementation

quick snow
#

Okay, that makes sense.

#

So within CPython itself one wouldn't really run into needing the Python implementations

clear hill
tidal sorrel
clear hill
#

maybe, it could also be C code that expects a Python object

raven ridge
tidal sorrel
#

and not run python code that has no effect on the interpreter

deep dirge
raven ridge
#

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

clear hill
#

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 🙂

raven ridge
#

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

tidal sorrel
#

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?

raven ridge
#

yes, all the time

#

a big part of the choice between writing stdlib modules in Python vs C is convenience

clear hill
#

yeah, writing correct C is really hard

raven ridge
#

the threading module, for instance, is written in Python

clear hill
#

I don’t think there are any builtins written in Python though

deep dirge
fallen slateBOT
#

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");```
hot gulch
#

c and python?

#

not a good mix lol

tidal sorrel
#

yup

clear hill
clear hill
raven ridge
clear hill
#

numpy’s array printing is all written in Python

raven ridge
#

oh, traceback printing in general is another good example of this tbh

clear hill
#

no one wants to write fiddly string formatting code in C

raven ridge
#

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

hot gulch
#

lol

#

jk

#

everyone likes what they like

raven ridge
#

most professional devs regularly write code in many different languages. It's a "right tool for the right job" sort of thing

clear hill
#

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

raven ridge
#

hash() often will as well

quick snow
#

If the builtins added by site count, then license and copyright are pure Python :P

raven ridge
#

help() is like breakpoint() - written in C, but immediately delegates to Python code

quick snow
#

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)

raven ridge
#

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

quick snow
#

👍

feral island
raven ridge
#

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

tidal sorrel
# raven ridge at least `encodings` and `linecache`
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

raven ridge
#

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

tidal sorrel
#

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.
>>>
raven ridge
#

oh, or maybe not VS Code, maybe that's the MS App Store version of Python. One or the other

tidal sorrel
#

im using wsl

raven ridge
#

yes, but WSL could still be running the app store version of the interpreter, depending on your PATH

rose schooner
copper crescent
#

can we have a pep for april 1st that changes def to fun?

radiant garden
stable grail
#

@teal grotto ill share it here

teal grotto
stable grail
teal grotto
#

...why is math.pow so much faster?

stable grail
#

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
teal grotto
#

😕

stable grail
#

exponental x ** 2 is this

#
0 LOAD_FAST                0 (x)
2 LOAD_CONST               1 (2)
4 BINARY_POWER
6 RETURN_VALUE
teal grotto
stable grail
#

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;
}
stable grail
#

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

feral island
#

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.

stable grail
#

math is float focused, so that does make sense

feral island
#

Would be interesting to see performance comparisons between doing ** on floats and math.pow

stable grail
#

maybe we should just do it

feral island
#

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

stable grail
#

but as ned commented in the other channel

fallen slateBOT
#

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```
stable grail
#

Karatsuba multiplication is something i know off, but its been decades since i actually looked into the details

spark magnet
#

another surprising thing: if the numbers get above a certain size, the C code calls _pylong.py to do things like str->int conversion.

tidal sorrel
#

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;
}
raven ridge
raven ridge
tidal sorrel
#

I dont even know what that will do

raven ridge
#

!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=}")

fallen slateBOT
raven ridge
#

note that all of these indexes are 1 based, not 0 based, so that's pointing to the )

tidal sorrel
#

is this related to the issue I presented?

raven ridge
#

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

tidal sorrel
#

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

raven ridge
#

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"?

tidal sorrel
drowsy barn
#

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.
drowsy barn
#

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.
hybrid relic
#

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

drowsy barn
#

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()
}```
radiant garden
#

Are you using AI to write these messages?

drowsy barn
drowsy barn
drowsy barn
radiant garden
#

it shows

hybrid relic
drowsy barn
#

It's tough not to be influence by it, it's everywhere, AI

hybrid relic
#

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

drowsy barn
#

Oh we need C/C++ assist for this feature

#

definitely

hybrid relic
#

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

drowsy barn
#

So I'm proposing using function pointer and dispatch tables in C/C++ for reference implementation

drowsy barn
#

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

drowsy barn
drowsy barn
# hybrid relic But still, the main problem of performance (Which is what I'm assuming is the ma...

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

drowsy barn
#

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

tidal sorrel
#

what is the "OP" token used for?

gilded flare
tidal sorrel
radiant garden
#

It's really a catch-all for all punctuation

hybrid relic
#

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

feral island
raven ridge
#

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

hybrid relic
feral island
#

if you put more frames on the native stack you are more likely to overflow the native stack, right?

raven ridge
#

Are you suggesting that it should use alloca() or something?

hybrid relic
#

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)

hybrid relic
raven ridge
#

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?

hybrid relic
#

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

raven ridge
#

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

hybrid relic
#

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

hybrid relic
raven ridge
#

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

hybrid relic
#

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

feral island
hybrid relic
hybrid relic
fallen slateBOT
#

InternalDocs/frames.md?plain=1 line 18

Python semantics allows frames to outlive the activation, so they need to```
round path
raven ridge
#

but the Python frame object is lazily created already. I thought we're talking about the interpreter frame, not the Python frame object

hybrid relic
#

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

round path
raven ridge
#

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

round path
#

Hmm I don't recall exactly now, but i think if they copy youre right that it would be possible

hybrid relic
#

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

raven ridge
#

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

round path
raven ridge
#

oh, yes, wait, you're right

#

my second clause was wrong

round path
#

TheShermanTanker is right in that the only reason we used the C stack is that the eval function was recursive

raven ridge
#

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

round path
raven ridge
#

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.

fallen slateBOT
#

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.```
raven ridge
#

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

round path
#

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

raven ridge
#

"trampoline" would probably be a better word, but I'm sure that's overloaded thanks to ctypes...

#

there's only so many good words 😆

round path
#

There's trampoline in the perf jit and the normal jit too

hybrid relic
#

There's more than 1 compiler?

raven ridge
#

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:

  1. when allocating a new interpreter frame from the eval loop, use alloca()
  2. add a new frame->owner flag to represent alloca() frames
  3. 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
  4. profit?
hybrid relic
#

I think there's another snag that I just realized

raven ridge
#

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

round path
hybrid relic
#

How are we going to drop the frames once the scope ends? I don't think alloca allocated memory can be freed manually

raven ridge
#

DWARF debug info is itself a bytecode language describing a stack machine

hybrid relic
#

I'm going to continue trying ideas out to see what sticks in the meantime :)

raven ridge
#

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()

alpine rose
tidal sorrel
#

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

tidal sorrel
#

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

raven ridge
#

huh. That does look weird to me.

#

!e ```py
1x

fallen slateBOT
# raven ridge !e ```py 1x ```

:x: Your 3.14 eval job has completed with return code 1.

001 |   File "/home/main.py", line 1
002 |     1x
003 |     ^
004 | SyntaxError: invalid decimal literal
raven ridge
#

!e ```py

fallen slateBOT
# raven ridge !e ```py 1Ã ```

:x: Your 3.14 eval job has completed with return code 1.

001 |   File "/home/main.py", line 1
002 |     1Ã
003 |      ^
004 | SyntaxError: invalid syntax
raven ridge
#

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 for as it would give for 1 Ã - which is not the same error as it gives for 1A

rose schooner
#

it doesn't seem like unicode digits are considered valid so the ascii check seems to be quite pointless

raven ridge
#

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

tidal sorrel
#

why does the tokenizer read raw bytes from the source code instead of unicode codepoints?

boreal umbra
#

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

raven ridge
#

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)

crimson hatch
raven ridge
#

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

crimson hatch
#

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

raven ridge
#

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?

fallen slateBOT
#

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);```
raven ridge
#

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 😆

whole jewel
#

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?

  1. is .args the standard for showing exception message? Or having a __str__() on any sub-exception class is okay and recommendable too?

  2. does not populating .args can have side effects? If any, does having a __str__() on any sub exception class is enough for exception message?

  3. 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's args attribute ?

  4. 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.

raven ridge
#

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__.

whole jewel
#

Ooh