Repository navigation
Typing: undocumented behaviour change for protocols decorated with @final and @runtime_checkable in 3.11 #103171
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or errorstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory3.11only security fixesonly security fixes3.12only security fixesonly security fixes
on Apr 1, 2023 - changed the title
[-]Undocumented behaviour change for protocols decorated with `@final` and `@runtime_checkable` in 3.11[/-][+]Typing: undocumented behaviour change for protocols decorated with `@final` and `@runtime_checkable` in 3.11[/+]on Apr 1, 2023 My first instinct is to leave this as is. Marking Protocols as final is a weird thing to do in the first place (the whole point of protocols is to subclass from them, though usually implicitly). Adding more names to that
_TYPING_INTERNALSlist is risky because if people use those names in their protocols, we'll silently ignore them. You could imagine a Protocol that is meant to match only classes decorated with@final.This might be a bigger issue for PEP-702 though: what if you want to
@deprecateda runtime-checkable Protocol? That makes more sense than making it final, so I'll amend the PEP to say that__deprecated__should be ignored by@runtime_checkable.Reacted by Alex Waygood and sunmy2019My first instinct is to leave this as is. Marking Protocols as final is a weird thing to do in the first place (the whole point of protocols is to subclass from them, though usually implicitly). Adding more names to that
_TYPING_INTERNALSlist is risky because if people use those names in their protocols, we'll silently ignore them. You could imagine a Protocol that is meant to match only classes decorated with@final.Sure. In that case, I think we should probably document the behaviour change with a
.. versionchangednotice in the docs somewhere — sound good?Sure, that's fine.
Reacted by Alex WaygoodI mark all things as
@finalby default. Because inside an internal code-base no things should be extended without a good reason.So, I would consider this as a bug.
__final__is an internal thing that should not be exposed to users and should not affect the runtime of regular operations.__final__is an internal thing that should not be exposed to users and should not affect the runtime of regular operations.I'm not sure that makes sense. The fact that
@finalsets the__final__attribute is documented, and I don't think we ever intended it to be an internal thing.typing.pydoesn't need the attribute to be set at all; the only reason why we introduced the behaviour in 3.11 where it sets the attribute wherever possible was so that third-party tools could more easily introspect whether a method or class had been decorated with@final. That's the opposite of it being an internal thing. https://docs.python.org/3/library/typing.html#typing.finalPersonally, I wasn't initially sure this new behaviour makes sense for
isinstance(), but I can see arguments either way. What really concerns me is the new behaviour withissubclass(), which seems pretty unfortunate to me.Reacted by sunmy2019@ilevkivskyi, do you have any opinions on how
@finaland@runtime_checkableshould interact at runtime?I haven't tried, but won't the change from #103160 fix this issue, because the
__final__attribute is added after we compute the set of attributes?I haven't tried, but won't the change from #103160 fix this issue, because the
__final__attribute is added after we compute the set of attributes?Good point, #103160 does indeed fix this issue!
I wasn't planning on backporting #103160 (if it's even accepted), though, as I was thinking of it as a performance optimisation (and it does change behaviour in a few other subtle ways, as discussed in the PR thread). So if #103160 is merged, that means that 3.12 will have the same behaviour as we had in 3.10 for
@finalruntime-checkable protocols, but 3.11 will be the "odd one out".FWIW I think protocols should not be final. So the behaviour change is fine.
Maybe we should just add a note to the docs for
@finalsaying that combining the decorator with@runtime_checkableisn't supported and might have unpredictable consequences.I think protocols should not be final
But, they can be.
What is the reason not to ignore
__final__attribute? 🤔
It surely does not affect@runtime_checkablein any other manner.I think that having an expected default behaviour in this case is easier than writting a warning in the docs.
Good point, #103160 does indeed fix this issue!
I wasn't planning on backporting #103160 (if it's even accepted), though, as I was thinking of it as a performance optimisation (and it does change behaviour in a few other subtle ways, as discussed in the PR thread). So if #103160 is merged, that means that 3.12 will have the same behaviour as we had in 3.10 for
@finalruntime-checkable protocols, but 3.11 will be the "odd one out".#103160 has now been merged, so it is just 3.11 that has the behaviour change now.
I think we should fix 3.11 so that it excludes
__final__when looking at protocol compatibility, as that will make 3.11 behave consistently with both 3.10 and 3.12.Reacted by Alex Waygood
Python 3.11 introduced an undocumented behaviour change for protocols decorated with both
@finaland@runtime_checkable. On 3.10:On 3.11:
This is because, following 0bbf30e (by @JelleZijlstra), the
@finaldecorator sets a__final__attribute wherever it can, so that it is introspectable by runtime tools. But the runtime-checkable-protocolisinstance()machinery doesn't know anything about the__final__attribute, so it assumes that the__final__attribute is just a regular protocol member.This should be pretty easy to fix: we just need to add
__final__to the set of "special attributes" ignored by runtime-checkable-protocolisinstance()/issubclass()checks here:cpython/Lib/typing.py
Lines 1906 to 1909 in 848bdbe
@JelleZijlstra do you agree with that course of action?
Linked PRs
@final#103173@final#105445@final#105473@final(GH-105473) #105474