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
{{ message }}
Repository navigation
sample use case: Type object with particular constructor signature #26
Annotating 'Type object with particular constructor signature' requires intersection.
Suppose we have a
# returns cached instance of SomeClass or creates a new instancedefget_cached_or_create(klass):
if_is_cached(klass):
return_get_cached(klass)
else:
print(f"creating instance for {klass}")
instance=klass()
_set_cached(klass, instance)
returninstance
However, it does not express the fact T's constructor must be callable without argument.
Above signature suggests get_cached_or_create can be called with type object with any constructor signature.
The desired typing of get_cached_or_create in fact is:
type[T] & Callable[[], T] can be seen as expressing constraints on the constructor of the T class, but it also appears to express complex metaclass behavior.
Specifying __init__ signature is not enough because the class can override __new__ or its metaclass's __call__ (I think mypy has limitations with them, but pyright handles them somewhat better).
I think we can do this to specify __new__ in Protocol.
or its metaclass's __call__
This would certainly be difficult to express with type annotations.
However, it's challenging to express or constrain metaclass behavior through static typing.
Therefore, I think specifying __init__ or __new__ signature in a Protocol is sufficient.
mypy has limitations with them
Certainly, I tried this on the mypy playground, it didn't result in an error.
Maybe this should be reported to the mypy developers.
it's challenging to express or constrain metaclass behavior through static typing.
Well, that's my point exactly. It will get much easier with intersections. Whatever additional behaviour you expect a type object to have, you will be able to just intersect a Callable or Protocol with the type type.
Why do you need an intersection here? Isn't it sufficient to use Callable[[], T] as the input type? In your example, you've intersected this type with type[T], but it's not clear to me what value that provides from a typing standpoint.
Isn't it sufficient to use Callable[[], T] as the input type?
Probably I have over simplified the example.
My actual case required an object with following behaviours at the least:
is callable with no argument and returns its instance
has __name__ attribute (used for error messages)
Hashable (used as cache key) to identify T
can be passed to isinstance (for an assertion, which is not strictly necessary)
can be passed to issubclass (once, there was a plan to return an instance of subclass if already cached)
The gist is, though, that the doc comment for the param states "class object with void constructor", and there is no type annotation that exactly matches that without intersection.
Keep in mind that prior decisions from type checkers not to enforce something for "pragmatic" reasons is shining a light on why this might be needed. Unlike many of the prior ones, I find the intersection-based solution if intersections are added sufficient to resolve this. (even though I think that this shouldn't be allowed, and that if a subclass needs alternate incompatible construction, it probably shouldn't claim to be a drop-in replacement and shouldn't be a subclass)
Activity
junkmd commentedon Sep 10, 2023
Hi!
I think it's better to bind the
Protocolto aTypeVarrather than usingIntersectionfor this.For example...
type[T] & Callable[[], T]can be seen as expressing constraints on the constructor of theTclass, but it also appears to express complex metaclass behavior.ippeiukai commentedon Sep 10, 2023
Specifying
__init__signature is not enough because the class can override__new__or its metaclass's__call__(I think mypy has limitations with them, but pyright handles them somewhat better).junkmd commentedon Sep 10, 2023
I think we can do this to specify
__new__inProtocol.This would certainly be difficult to express with type annotations.
However, it's challenging to express or constrain metaclass behavior through static typing.
Therefore, I think specifying
__init__or__new__signature in aProtocolis sufficient.Certainly, I tried this on the mypy playground, it didn't result in an error.
Maybe this should be reported to the mypy developers.
ippeiukai commentedon Sep 10, 2023
Well, that's my point exactly. It will get much easier with intersections. Whatever additional behaviour you expect a
typeobject to have, you will be able to just intersect a Callable or Protocol with thetypetype.junkmd commentedon Sep 10, 2023
I understand. Indeed, your approach is necessary to cover those cases.
Providing examples of what is valid and invalid would make it more clear.
Is my example OK?
ippeiukai commentedon Sep 10, 2023
Looks good. Exactly what I was thinking.
Thank you for clarifying!
erictraut commentedon Sep 10, 2023
Why do you need an intersection here? Isn't it sufficient to use
Callable[[], T]as the input type? In your example, you've intersected this type withtype[T], but it's not clear to me what value that provides from a typing standpoint.ippeiukai commentedon Sep 10, 2023
Probably I have over simplified the example.
My actual case required an object with following behaviours at the least:
__name__attribute (used for error messages)isinstance(for an assertion, which is not strictly necessary)issubclass(once, there was a plan to return an instance of subclass if already cached)The gist is, though, that the doc comment for the param states "class object with void constructor", and there is no type annotation that exactly matches that without intersection.
mikeshardmind commentedon Sep 12, 2023
Keep in mind that prior decisions from type checkers not to enforce something for "pragmatic" reasons is shining a light on why this might be needed. Unlike many of the prior ones, I find the intersection-based solution if intersections are added sufficient to resolve this. (even though I think that this shouldn't be allowed, and that if a subclass needs alternate incompatible construction, it probably shouldn't claim to be a drop-in replacement and shouldn't be a subclass)
ippeiukai commentedon Sep 13, 2023
So, continuing from example given in #26 (comment):