-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ABCMeta.register support #2922
Comments
This was on my radar early in mypy development. I didn't work on this back then because this is a tricky feature. For example, if module There are few potential ways to support this:
|
It seems likely that mypy will support structural subtyping before it supports registering ABCs. Structural subtyping/protocols can be used for a similar effect, but that should be easier to implement. See python/typing#11 for more information. |
Alternatively, instead of doing something very complicated to make sure |
I just ran into a use case where support for
The basic problem is that we hacked the class inheritance syntax to do typing-related stuff. But this means we are limited in what we can do - and in particular, we can't use forward declarations there (as well as other constructs that would mess up inheritance; at present, I'm not aware of any specific situations where this comes up). Since forwarding syntax is already set in stone, the only solution I see is to make this work:
It looks a little ugly, but at least it's logical. True, this particular use case will disappear once Protocols (#3132) is merged. But it won't fully solve the problem because sometimes the user might want to rely on nominal rather than structural typing. Incidentally, this situation already appears in
It works there because it's a stub, not the original source code, so python runtime doesn't need to look at it. (Btw, we should probably make mypy catch inheritance from a string literal before the runtime complains.) |
class A(Iterable['A']):
... works both in mypy and at runtime. Btw, what are the cases where you think people will want nominal checks instead of protocols? |
Duh 😆 Nominal checks: if the method naming is too generic ( |
Any news/updates on this? I would have really liked support for |
Numeric tower inter-operating with the builtin types requires nominal typing and requires this functionality. People wanting to create nominal subtypes that do not want inherit their parent's implementation also requires this functionality. I have a need for the second reason. class Logic:
"""4-value logic type as seen in Verilog and VHDL: 0, 1, X, and Z"""
class Bit(Logic):
"""2-value logic type: 0 and 1"""
class LogicArray(Sequence[Logic]):
"""Array of Logics that constructs literals or subtypes to enforce the invariant"""
class Sfixed(Sequence[Bit]):
"""Two's complement arbitrarily-bounded fixed point number as seen in VHDL"""
# can't inherit from LogicArray since the generic base class needs to be re-parameterized
# don't want to anyway since Sfixed is implemented with an integer since that's a whole lot faster than a list of user-defined types
LogicArray.register(Sfixed) Of course the real issue here is that inheritance is a sucky mechanism since it violates separation of concerns. I'd much rather say " |
Most of the use cases I'm seeing here are for cases where people are using Special casing uses of
|
Isn't that the least important case since anyone who wants to support this can simply make the ABC a base class? The biggest use case, I think, for this feature would be the numeric tower working as intended. |
The important difference between using
My understanding of #3186 is that the builtin number types don't properly implement the numeric tower types that they claim to implement (like in python/typeshed#3195 (comment)), and runtime works around that by registering the classes with the correct ABCs. In that context, having those builtin number types directly subclass the relevant ABCs isn't a realistic option because that would most likely cause errors about unimplemented methods everywhere that those number types are used, even though essentially all of that code is safe. Using |
Is this still true?
Are you sure about this? You maybe defining a class hierarchy and want to define an ABC, which you will then have your final classes inherit from it.
So you want to change the semantics of |
I think you can just add in the appropriate interface class, and your example will work: class Logic:
"""4-value logic type as seen in Verilog and VHDL: 0, 1, X, and Z"""
class Bit(Logic):
"""2-value logic type: 0 and 1"""
class LogicArrayInterface(Sequence[Logic]): ... # define any members you want to share.
class LogicArray(LogicArrayInterface):
"""Array of Logics that constructs literals or subtypes to enforce the invariant"""
class Sfixed(LogicArrayInterface, Sequence[Bit]):
"""Two's complement arbitrarily-bounded fixed point number as seen in VHDL""" No need for ABC registration. Ordinary inheritance works and the concerns are perfectly separated. |
@NeilGirdhar That's not equivalent. Sfixed is a subtype of LogicArray by its nature. Not only is it intuitively not correct, it will create more work. For example, lets say you have a bitwise OR operation that should work on all class LogicArray(LogicArrayInterface):
"""Array of Logics that constructs literals or subtypes to enforce the invariant"""
# I don't know the concrete type of the other argument so I don't know the correct type to return.
def __or__(self, other: LogicArrayInterface) -> ???:
...
# This would work, but it requires that Sfixed be a subtype of LogicArray, that is no longer the case.
# I can get this to work at runtime using `ABC.register`, but it isn't supported statically.
def __or__(self: Self, other: Self) -> Self:
...
def __ror__(self: Self, other: Self) -> Self:
...
# Oh dear... this does work though.
@overload
def __or__(self, other: LogicArray) -> LogicArray: ...
@overload
def __or__(self, other: X01ZArray) -> LogicArray: ...
@overload
def __or__(self, other: BitArray) -> LogicArray: ...
@overload
def __or__(self, other: Signed) -> LogicArray: ...
@overload
def __or__(self, other: Unsigned) -> LogicArray: ...
@overload
def __or__(self, other: Sfixed) -> LogicArray: ...
@overload
def __or__(self, other: Ufixed) -> LogicArray: ...
@overload
def __or__(self, other: Float) -> LogicArray: ...
# "let's add another type!"
# *audible groans*
# *someone posts something about the expression problem* Also, there's a problem in Sfixed with the inheritance hierarchy you have set, |
I see now. Your problem is analogous to numpy arrays, which are generic on the "dtype". Essentially, X = TypeVar('X', bound=Logic)
class LogicArray(Sequence[X], Generic[X]):
@overload
def __or__(self, other: Self) -> Self:
...
@overload
def __or__(self, other: LogicArray[Any]) -> LogicArray[Any]:
... If you need more clever type promotion, then I'd look at how |
Summarizing the state of the world + conversation here: Jukka's comment here still very much applies: #2922 (comment). There is not a great way to support If you control your type, you can simply inherit from the ABC you want:
If you can't actually inherit at runtime, you can lie to the type checker:
If you want mypy to check that your MyInt is actually a conformant implementation, you can simply instantiate it:
Curious if that covers all the use cases where you control your type, or if there's additional functionality that |
That will also not work, unfortunately. Subtypes have more operations on them. For example, @hauntsaninja |
Totally agree with shooting for number 3.
Yes, okay, that makes sense! |
Mypy doesn't understand `@abc.register`: python/mypy#2922 so we should subclass the abc directly instead. This requires using a custom metaclass which is simultaneously a subclass of JsProxy and ABCMeta.
Mypy could support cases like this by special casing
ABCMeta.register
calls.__subclasshook__
,__subclasscheck__
, and__instancecheck__
are more troublesome and could be left ignored.The text was updated successfully, but these errors were encountered: