3.12 slot classes wishlist

Hi there.

I’m one of the maintainers of attrs so that’s the perspective I’m posting this from, but these feature requests are wholly applicable to dataclasses as well.

This is my wishlist for making slot classes better in the two libraries, since I’m a big fan of slot classes. I’m reaching out to core devs to see if any of these are possible.

  1. Add slotness to a class after the fact.

Right now, when creating a slot class, we need to recreate the class from scratch to add slots to it. We’ve tried to make this as smooth as possible, but new edge cases keep popping up, like needing to rewrite closure cells in deeply decorated methods, and class.__subclasses__() still containing the old class until a GC collection.

It would be awesome if there was a way to add slotness to a class instead of needing to clone it. It could be a low-level thing, tucked away somewhere uninteresting to civilians. We only need it one-way, dict → slots.

  1. Speed up frozen slot __init__ by creating slot descriptors for read-only fields

Right now, frozen slot classes incur a slight speed penalty on creation. (IIRC dict classes avoid this by using the instance __dict__ directly.)

This is because frozen classes have a special __setattr__ to simulate immutability. So inside __init__, slot classes use a cached version of object.__setattr__ to set their fields for the first time. This is slightly slower than the non-frozen path.

Frozen classes are a great tool for eliminating some classes of bugs and it bothers me I need to pick between performance and (a slightly better chance of) correctness, even though the performance penalty is minor.

To give context, here’s my understanding of how slot classes work (corrections welcome). Each field value is stored somewhere hidden, and when a slot class is created the interpreter creates a descriptor for each field and puts it on the class, under the field name. This magical descriptor handles writes and reads from each field.

My proposal: the interpreter should expose a function that can copy this descriptor into a read-only clone. Attrs and dataclasses could then stash away the original descriptors into a well-known location (they are still needed inside of __init__), and put the clone, read-only descriptors on the class.

Thoughts welcome :pray:

5 Likes

To #1: At one point we talked about not applying “slot-ness” (creating descriptors, probably other stuff) until the first instance of a class was created. That way __slots__ could be modified after class creation and before creating an instance. This never got further than conjecture. There are no doubt edge cases for code that inspects a class before instances exist. I haven’t asked other implementations if this would cause them problems.

This seems fine to me. The reason we don’t want to change the slots after an instance is in existence is that the slot accessor doesn’t do range checking on the index it uses to access the attribute value. But as long as no instances exist it should be okay to increase the slot count. We couldn’t decrease it safely though.

I guess the trick is determining if an instance of a class (or instances of subclasses) exist. I haven’t looked in to it. Maybe we can steal a bit in the class to keep track, and set it every time an instance is created.