You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
(This is an extension to #241 and copied from the discussion there.)
What about having a magic class decorator (with no or minimal runtime effect) or a base class that declares a class as a "pure mixin"? A pure mixin could only be used for implementation inheritance, and it wouldn't be type checked by itself at all -- it would only be type checked as part of implementation inheritance. I think that this would allow type checkers to check some mixin use cases that are tricky right now with minimal code changes. The alternative would be to use ABCs to declare the things that the mixin expects to be defined elsewhere, but they are harder to use and arguably add boilerplate.
Example:
@puremixin
class MyMixin:
def do_stuff(self) -> int:
self.one_thing() # ok, even though not defined in this class
return self.second_thing(1) # also ok
class Thing(Implementation[MyMixin]):
def one_thing(self) -> None: pass # ok
def second_thing(self, x: int) -> str: # error: return type not compatible with do_stuff
return 'x'
I think that omitting the signatures for one_thing and second_thing in MyMixin goes a bit about the documentation and clarity purposes of static typing (which I understand are the main goals of the
typing initiative in Python).
Agreed that the code is not super clear, as there is no explicit documentation about what the mixin expects. I probably wouldn't use this for new code -- it would mainly be aimed at making it easier to migrate legacy code to static typing.
I propose that classes with this decorator will be automatically recognized as implementation base class wherever it appears, i.e., wrapping in Implementation[...] is not necessary.
I propose to name decorator @implementation and call such classes implementation base classes (this is in some sense could be a complementary notion to abstract base classes, that define interfaces). In principle, using @Implementation (with capital letter i) could be even better. But then we should also use Implementation(MyClass) in base class lists as @dmoisset proposes in Allow subclassing without supertyping #241.
fromtypingimportType, TYPE_CHECKING, TypeVarT=TypeVar('T')
defwith_typehint(baseclass: Type[T]) ->Type[T]:
""" Useful function to make mixins with baseclass typehint ``` class ReadonlyMixin(with_typehint(BaseAdmin))): ... ``` """ifTYPE_CHECKING:
returnbaseclassreturnobject
Example tested in Pyright:
classReadOnlyInlineMixin(with_typehint(BaseModelAdmin)):
defget_readonly_fields(self,
request: WSGIRequest,
obj: Optional[Model] =None) ->List[str]:
ifself.readonly_fieldsisNone:
readonly_fields= []
else:
readonly_fields=self.readonly_fields# self get is typed by baseclassreturnself._get_readonly_fields(request, obj) +list(readonly_fields)
defhas_change_permission(self,
request: WSGIRequest,
obj: Optional[Model] =None) ->bool:
return (
request.methodin ['GET', 'HEAD']
andsuper().has_change_permission(request, obj) # super is typed by baseclass
)
>>>ReadOnlyAdminMixin.__mro__
(<class'custom.django.admin.mixins.ReadOnlyAdminMixin'>, <class'object'>)
Reacted by lovetoburnswhen, jxzhang, Kai Schlamp and Sasta Dev
@leonardon473 This is a nice trick, but it can't work with static type checkers, unfortunately. E. g. mypy throws Unsupported dynamic base class "with_typehint" error.
I usually want to declare a Mixin that is common for classes with some common superclass (let's say Django generic views), so I'd like an api like this:
@mixin(Thing) # or @baseclass(Thing)classMyMixin:
x=1defdo_stuff(self) ->int:
returnself.one_thing() # ok, because it's defined in Thing classdefdo_other_stuff(self) ->int:
returnself.y# fail, there is no attribute `y` in Thing or MyMixinclassThing(MyMixin):
defone_thing(self) ->int:
return1defsecond_thing(self, x: int) ->str:
returnself.x# ok, because `x` is defined in MyMixin
Reacted by Kound, Florian Demmer, Kai Schlamp and Alexander Volkovsky
Now to my Use Case:
Writing MixIn Classes for an existing code base of an Third Base project (i.e. Django, unittest).
So defining a protocol manually as suggested by MyPy won't work. Doing so, I would miss interface changes of the third party package with the type checker.
So what I actually looking for is a command that generates the Protocol of the third Party library for me.
Doing so I would get type hints i.e. from PyCharm and type checking warning ones the interface of the third party tool changes.
So actually @JukkaL proposal of just ignoring the type checks would not satisfy my use case here.
A MixIn should be defined properly, so that it is clear for which classes that MixIn is intended for.
As an example. Imaging I want to test implementations of a Baseclass with unittest. (The Same Use case would it be with a lot of the Django Mixins)
When testing the MixIn itself TypeCheckers and TypeHinters (like PyCharm) shall assume that all variables and functions are implemented as if I would have used TestingBaseMixin(TestCase)
When checking any class that is actually based on a MixIn. It should make sure that the required that TestCase is actually part of the MRO. If this is not the there shall be a error given to use.
If it is used to defined just another MixIn, the @mixin(Class) decorator is shall be required. So all MixIns are defined well.
Probably there are some edges cases here. Like what if you plan write a MixIn that can be applied to two different Bases?
Another idea would be to implement something like create_protocol_from_class that creates a protocol from an existing class.
Any suggestions on how to bring this topic (Propper type checks with MixIn from Third Party packages) forward?
Shall this be kept in this Issue or better open another one?
Reacted by Jim King, Anthony King, Jameel Al-Aziz, Sandro Rodrigues, Pedro Silva, Iván || HacKan, samsja, Daniel Atanasov and Kai Schlamp
Years have passed ... and it is still challenging to get the type hints of mixins correctly (if possible at all). Are there any new approaches to type mixins? The solution @leonardon473 came up with seems to work with pyright, but not with mypy.
Since this was originally opened, protocols have been added to the type system. Here's a solution to Jukka's original problem using a protocol with a partial implementation. It enforces that the final class, which includes the mixin as a base class, implements the methods that are assumed by the mixin implementation.
fromtypingimportProtocolclassMyMixin(Protocol):
defdo_stuff(self) ->str:
self.one_thing()
returnself.second_thing(1)
# The following methods must be implemented by the final class.defone_thing(self) ->None:
...
defsecond_thing(self, val: int) ->str:
...
classThing(MyMixin):
defone_thing(self) ->None:
passdefsecond_thing(self, val: int) ->str:
return"x"
Indeed this is a little improvement but I still need to duplicate things, as I have to create a protocol for existing classes.
And I have to manually verify the my protocol is identical to the external class I want to write an Mixin for.
I could do something like
fromtypingimportTYPE_CHECKING, ProtocolfromextralibimportExtraclassMixinProto(Protocol):
foo: strifTYPE_CHECKING:
_: MixinProto=Extra() # hopefully the class __init__ allows for that...
But this is rather suboptimal and work intensive.
And I don't see that
It enforces that the final class, which includes the mixin as a base class, implements the methods that are assumed by the mixin implementation.
If the final class inherits our Mixin but doesn't provide the given attribute, there won't be any typing errors given...
Activity
dmoisset commentedon Jul 14, 2016
(copying some items from my comments in #241)
I think that omitting the signatures for
one_thingandsecond_thinginMyMixingoes a bit about the documentation and clarity purposes of static typing (which I understand are the main goals of thetyping initiative in Python).
JukkaL commentedon Jul 14, 2016
Agreed that the code is not super clear, as there is no explicit documentation about what the mixin expects. I probably wouldn't use this for new code -- it would mainly be aimed at making it easier to migrate legacy code to static typing.
ilevkivskyi commentedon Jul 14, 2016
I like this idea, here are few comments:
Implementation[...]is not necessary.@implementationand call such classes implementation base classes (this is in some sense could be a complementary notion to abstract base classes, that define interfaces). In principle, using@Implementation(with capital letter i) could be even better. But then we should also useImplementation(MyClass)in base class lists as @dmoisset proposes in Allow subclassing without supertyping #241.leonardoramirezr commentedon Jul 9, 2020
Try with:
Example tested in Pyright:
stinovlas commentedon Jul 10, 2020
@leonardon473 This is a nice trick, but it can't work with static type checkers, unfortunately. E. g. mypy throws Unsupported dynamic base class "with_typehint" error.
I usually want to declare a Mixin that is common for classes with some common superclass (let's say Django generic views), so I'd like an api like this:
CarliJoy commentedon Dec 6, 2020
Actually the idea described by @leonardon473 and @stinovlas
Just a reference here some related Issues from StackOverflow:
Now to my Use Case:
Writing MixIn Classes for an existing code base of an Third Base project (i.e. Django, unittest).
So defining a protocol manually as suggested by MyPy won't work. Doing so, I would miss interface changes of the third party package with the type checker.
So what I actually looking for is a command that generates the Protocol of the third Party library for me.
Doing so I would get type hints i.e. from PyCharm and type checking warning ones the interface of the third party tool changes.
So actually @JukkaL proposal of just ignoring the type checks would not satisfy my use case here.
A MixIn should be defined properly, so that it is clear for which classes that MixIn is intended for.
As an example. Imaging I want to test implementations of a
Baseclasswith unittest. (The Same Use case would it be with a lot of the Django Mixins)What would I expect from
@mixin(TestCase):Probably there are some edges cases here. Like what if you plan write a MixIn that can be applied to two different Bases?
Another idea would be to implement something like
create_protocol_from_classthat creates a protocol from an existing class.Any suggestions on how to bring this topic (Propper type checks with MixIn from Third Party packages) forward?
Shall this be kept in this Issue or better open another one?
medihack commentedon Oct 18, 2023
Years have passed ... and it is still challenging to get the type hints of mixins correctly (if possible at all). Are there any new approaches to type mixins? The solution @leonardon473 came up with seems to work with
pyright, but not withmypy.erictraut commentedon Oct 18, 2023
Since this was originally opened, protocols have been added to the type system. Here's a solution to Jukka's original problem using a protocol with a partial implementation. It enforces that the final class, which includes the mixin as a base class, implements the methods that are assumed by the mixin implementation.
CarliJoy commentedon Oct 19, 2023
Indeed this is a little improvement but I still need to duplicate things, as I have to create a protocol for existing classes.
And I have to manually verify the my protocol is identical to the external class I want to write an Mixin for.
I could do something like
But this is rather suboptimal and work intensive.
And I don't see that
If the final class inherits our Mixin but doesn't provide the given attribute, there won't be any typing errors given...