3 ms·
A while ago, PEP 591 [1] made its way into the language. It introduced a "final" decorator / type annotation to indicate to mypy that a certain class shouldn't
by _dain_ 5y ago
A while ago, PEP 591 [1] made its way into the language. It introduced a "final" decorator / type annotation to indicate to mypy that a certain class shouldn't be subclassed, or a certain method shouldn't be overridden. At the bottom, there is the comment:
>A related feature to final classes would be Scala-style sealed classes, where a class is allowed to be inherited only by classes defined in the same module. Sealed classes seem most useful in combination with pattern matching, so it does not seem to justify the complexity in our case. This could be revisited in the future.
Has this been picked up? Replacing long if-elif blocks is only half the power of pattern matching. The other is to have exhaustiveness-checking by the static type checker.
I have pieces of code that would really benefit from this, and I was looking forward to migrating it to the new pattern matching feature. But I can't find any word on whether this is being revisited. That's disappointing.
[1] https://www.python.org/dev/peps/pep-0591/ https://www.python.org/dev/peps/pep-0591/
- 3pt14159 5y agoThis is precisely the thing I do not want from python. Subclassing is super useful in convoluted tests where fakes are superior to mocks. I don't like being told "you can't do this" by some library's code.
- scoopertrooper 5y agoIf it's enforced at the type-level then, don't check types during your test runs? shrugs
- _dain_ 5y agoIt would be enforced by the optional static type checker, not the language runtime.
- Spivak 5y agoAnd if you want to enforce it at runtime you already can! class Final(type): def __new__(cls, name, bases, dct): if any(isinstance(base, Final) for base in bases): raise TypeError("No!") return type.__new__(cls, name, bases, dct) class NoSubclasses(metaclass=Final): pass class WillThrow(NoSubclasses): pass
- deleted 5y ago[deleted]
- wasyl 5y agoFrom my experience with Kotlin, sealed classes aren't really used for classes you'd like to fake in tests - they're usually used to have an exhaustive list of data types with different parameters, basically enums on steroids. Kotlin has recently added support for sealed interfaces, which would potentially suffer from the issue you described, but I haven't found a compelling use case (or any usage in wild) for sealed interfaces. If anything, I'd expect it to be used for types that explicitly shouldn't be faked by the library consumer anyway
- Spivak 5y agoYou can already have exhaustiveness-checking by the type checker right now today. from enum import Enum from typing import NoReturn class Stuff(Enum): Cat = 1 Dog = 2 Bear = 3 def exhaustiveness_check(value: NoReturn) -> NoReturn: raise AssertionError() def do_thing(stuff: Stuff) -> None: if stuff is Stuff.Cat: print("meow") elif stuff is Stuff.Dog: print("woof") else: exhaustiveness_check(stuff) And if you add a check for Bear it will validate it!
- _dain_ 5y agoOkay but what if I need something more than an enum? Like if the stuff parameter is of type "BaseThing", and there are only meant to be five subclasses of BaseThing to check. I would want the type checker to verify that I dealt with every case. But without sealed classes, it can't determine that some other code that subclasses from BaseThing is wrong.
- Spivak 5y agoAbsolutely! And since type checkers will likely never be able to implement support for __subclasscheck__ or __instancecheck__ the best we can ever do is annotations about the desired results of those methods. My only statement was that match isn't magic and you can do exhaustiveness checking (inasmuch as type checkers can detect it) without it.
- adsharma 5y agoSome more discussion in this thread https://mail.python.org/archives/list/typing-sig@python.org/thread/QSCT2N4RFPRQN2U7NIX6VCNVNDHGO22U/ https://mail.python.org/archives/list/typing-sig@python.org/... I have a fork of the "adt" library that supports sealed classes. For static type checking, I'm using a fork of typpete, a SMT based type checker. It's not as user friendly as mypy or pyright, but in the longer term can be a winner because it supports user defined type constraints (aka refinement types).