My time is short, so thank you for focusing on the real subject.
On Mon, 16 Aug 2021 at 11:00, Jeff Allen
I have spent a lot of time reading typeobject.c over the years I've been looking at an alternative implementation. It's quite difficult to follow, and full of tweaks for special circumstances. So I'm impressed with the understanding that "user2357112 supports Monica" brings to the subject.
I was impressed too. I suppose it worked on some C Extension and discovered the behaviour herself.
When a built-in type like dict is defined in C, pointers to its C implementation functions are hard-coded into slots in the type object. In order to make each appear as a method to Python, a descriptor is created when building the type that delegates to the slot (so sq_contains generates a descriptor __contains__ in the dictionary of the type.
Conversely, if in a sub-class you define __contains__, then the type builder will insert a function pointer in the slot of the new type that arranges a call to __contains__. This will overwrite whatever was in the slot.
In a C implementation, you can also define methods (by creating a PyMethodDef the tp_methods table) that become descriptors in the dictionary of the type. You would not normally define both a C function to place in the slot *and* the corresponding method via a PyMethodDef. If you do, the version from the dictionary of the type will win the slot, *unless* you mark the method definition (in its PyMethodDef) as METH_COEXIST.
This exception is used in the special case of dict (and hardly anywhere else but set I think). I assume this is because some important code calls __contains__ via the descriptor, rather than via the slot (which would be quicker), and because an explicit definition is faster than a descriptor created automatically to wrap the slot.
Now, when you create a sub-class, the table of slots is copied first, then the type is checked for definitions of special methods, and these are allowed to overwrite the slot, unless they are slot wrappers on the same function pointer the slot already contains. I think at this point the slot is re-written to contain a wrapper on __contains__, which has been inherited from dict.__contains__, because it isn't a *slot wrapper* on the same function. For example:
dict.__contains__
str.__contains__ class S(str): pass
S.__contains__
D.__contains__
And is it not possible for subclasses to continue to use the optimized version, if some contract will be maintained?