Consider allowing `@final` methods in Protocols
Feature
Allow this:
from typing import Protocol, final
class Sized(Protocol):
len: int
@final
def __len__(self) -> int:
return self.len
Pitch
I get that this is a bit unusual, but I don’t really see any harm that could come from this (as an aside, pyright allows it). My use case is that I’m using Protocols for mixins, and sometimes I want to make sure my mixed-in methods don’t get overwritten.
If you are using protocols for mixins, why not ABCs? To me, @final and non-inheritance subtyping (meaning you can provide a different implementation) feels like it's just asking for trouble although I can't think of any cases negatively impacted by this.
OTOH ABCs also offer virtual registering and I didn't check mypy for whether @final is allowed so the "bad" behavior might already be there (if mypy were to support that registering, that is :-).
If you are using protocols for mixins, why not ABCs?
With ABCs, there is the problem that I can't define abstract attributes, like the len: int in the example code above. I raised the question of abstract attributes in ABCs on typing-sig: https://mail.python.org/archives/list/[email protected]/thread/UAL6LMKDLUNFXSR7HGOXPK2KUFIROJES/ but there wasn't much interest for it.
Yeah, virtual registering really is a strange feature. I feel like most people just pretend it doesn't exist.
I feel like this should be separated into two parts:
from typing import Protocol, final
class MySized(Protocol):
len: int
def __len__(self) -> int: ...
class _MyImpl:
def __len__(self) -> int:
return self.len
class MyClass(_MyImpl):
def __init__(self, len: int) -> None:
self.len = len
I would prefer to keep Protocols free of implementation. They are signature declarations to me (just pure structure).
If I'd like to share the default implementation across protocol implementations, does it mean I have to do something like this:
import abc
from typing import Protocol, final
class MySized(Protocol):
len: int
def __len__(self) -> int: ...
class _MyDefaultImpl(abc.ABC, MySized):
@final
def __len__(self) -> int:
return self.len
class MyClass(_MyDefaultImpl):
def __init__(self, len: int) -> None:
self.len = len
MyClass(15)