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
The error message is generated because a class with a __call__ method (in this case, partial) isn't considered a subtype of Callable, even though it should be. See #797.
Also, partial only retains information about the return type, since the type system can't represent a more precise type.
the type system can't represent a more precise type.
You mean that the partial class doesn't represent it at runtime, right?
I think mypy could special case partial, and coerce it to the appropriate Callable type transparently
I meant that mypy would have to special case partial, since we can't write a good enough stub using PEP 484 features only. Mypy already does some special casing, but for this I'd rather have a more general mypy plugin/extension system instead of even more ad-hoc special case logic in the type checker.
Yes, this looks like a good candidate for function plugins. Increasing priority since this is may be pretty easy to implement now. Just supporting positional arguments for partial should cover the majority of uses and would probably be good enough.
This particular case and probably other basic use cases are already supported by protocols (see tests in PR #3132), simply because partial has __call__ and therefore is a structural subtype of Callable.
Protocols won't help with inferring the argument types of the result, though?
Yes, I didn't do any special-casing, partial in typeshed is only generic in one type _T which is the original return type. Maybe with the new support for flexible Callable this might be improved, but probably a plugin will still be required if we want precise types.
Another issue caused by this is that overloads don't work. It seems that the return type of partial(func, ...) is always the return type of the last @overload of func, regardless of arguments.
Not sure how TypeVars are supposed to work here, but it'd probably be safer to infer the type as Any or a Union of all @overloads, rather than taking an arbitrary return type.
My recent tests show that mypy is currently using the first@overload signature, rather than the last.
@AlexWaygood has reasonably judged #12675 to be a duplicate of the problem described in @Dreamsorcerer's comment. For anyone working on that problem: the now-closed issue (which I assume will continue to be available) contains some code to exercise the undesired behaviour.
fromtypingimportreveal_typefromfunctoolsimportpartialclassA:
passp=partial(A)
reveal_type(p) # Revealed type is "functools.partial[A]"reveal_type(p.func) # Revealed type is "def (*Any, **Any) -> A"print(issubclass(p.func, A))
# error: Argument 1 to "issubclass" has incompatible type "Callable[..., A]"; expected "type" [arg-type]
I don't know if this is considered a mypy bug or even related to partials, but the code runs fine, and issubclass returns True as expected.
def foo(a: Type[int], **kwargs):
x = a(1)
foo(functools.partial(int, base=10)) # Argument 1 to "foo" has incompatible type "partial[int]"; expected "Type[int]"
Admittedly, this could also be not type-safe:
foo(functools.partial(int, 5))
Not sure if there's a better way to handle this. My actual use case is to accept any subclass of an abstract type, but some implementations may need positional arguments and some not, so I use partial to include the positional arguments needed before passing into the function.
@Dreamsorcerer, I don't think your example above should type check. In other words, I think mypy is doing the right thing because partial[int] is not type compatible with type[int]. For example, if you attempt to pass a base keyword argument to partial[int], you will receive an error because base was already provided. You can make your code type safe by changing the definition of foo to the following, which type checks without error:
deffoo(a: Callable[..., int], **kwargs): ...
Reacted by Anton Agestam, Neil Girdhar and Ben Horn @ Tatari
def init_app(session_storage: Type[aiohttp_session.AbstractStorage]):
...
storage = session_storage(cookie_name="SESSION", max_age=3600, secure=True)
# First use case
init_app(aiohttp_session.SimpleCookieStorage)
# Second use case, which needs a positional key parameter
init_app(functools.partial(EncryptedCookieStorage, Fernet(config["fernet_key"]))
Would be nice not to lose typing on the kwargs (and I get an explicit Any error too).
So, actually, maybe the problem is that it allows a subclass of the abstract type to require an argument which the abstract class does not require...
In Python, constructors don't obey LSP. That's why you shouldn't annotate the function with type[...]: there's no way to know how with which parameters to construct it. You should annotate with Callable.
Right, but then we lose all typing for arguments.
You can type the arguments by providing them to Callable or by creating a protocol.
Activity
JukkaL commentedon May 5, 2016
The error message is generated because a class with a
__call__method (in this case,partial) isn't considered a subtype ofCallable, even though it should be. See #797.Also,
partialonly retains information about the return type, since the type system can't represent a more precise type.berdario commentedon May 5, 2016
You mean that the
partialclass doesn't represent it at runtime, right?I think mypy could special case
partial, and coerce it to the appropriateCallabletype transparentlyJukkaL commentedon May 5, 2016
I meant that mypy would have to special case
partial, since we can't write a good enough stub using PEP 484 features only. Mypy already does some special casing, but for this I'd rather have a more general mypy plugin/extension system instead of even more ad-hoc special case logic in the type checker.gvanrossum commentedon May 5, 2017
Maybe this could be supported by #3299
JukkaL commentedon Jun 13, 2017
Yes, this looks like a good candidate for function plugins. Increasing priority since this is may be pretty easy to implement now. Just supporting positional arguments for
partialshould cover the majority of uses and would probably be good enough.ilevkivskyi commentedon Jun 13, 2017
This particular case and probably other basic use cases are already supported by protocols (see tests in PR #3132), simply because
partialhas__call__and therefore is a structural subtype ofCallable.JukkaL commentedon Jun 13, 2017
Protocols won't help with inferring the argument types of the result, though?
ilevkivskyi commentedon Jun 13, 2017
Yes, I didn't do any special-casing,
partialin typeshed is only generic in one type_Twhich is the original return type. Maybe with the new support for flexibleCallablethis might be improved, but probably a plugin will still be required if we want precise types.19 remaining items
finite-state-machine commentedon Apr 26, 2022
My recent tests show that mypy is currently using the first
@overloadsignature, rather than the last.@AlexWaygood has reasonably judged #12675 to be a duplicate of the problem described in @Dreamsorcerer's comment. For anyone working on that problem: the now-closed issue (which I assume will continue to be available) contains some code to exercise the undesired behaviour.
raphCode commentedon Feb 15, 2023
I stumbled across this:
I don't know if this is considered a mypy bug or even related to partials, but the code runs fine, and
issubclassreturns True as expected.NeilGirdhar commentedon Feb 15, 2023
It's because in the typeshed, you have
So, the class is transformed into a callable on assignment. MyPy is doing the right thing here.
raphCode commentedon Feb 16, 2023
I see.
Is this the desired behavior? The typeshed does not seem to reflect what partial does at runtime in this case.
NeilGirdhar commentedon Feb 16, 2023
Yes, assigning to a member of a class can lose type information. Just like
tuple(list(('a', 'b')))gives youtuple[str, ...]and nottuple[str, str].This issue is about partial matching callable.
Dreamsorcerer commentedon Feb 25, 2023
Should this be a separate issue?
A partial of a type is not recognised as a type:
Admittedly, this could also be not type-safe:
Not sure if there's a better way to handle this. My actual use case is to accept any subclass of an abstract type, but some implementations may need positional arguments and some not, so I use partial to include the positional arguments needed before passing into the function.
erictraut commentedon Feb 25, 2023
@Dreamsorcerer, I don't think your example above should type check. In other words, I think mypy is doing the right thing because
partial[int]is not type compatible withtype[int]. For example, if you attempt to pass abasekeyword argument topartial[int], you will receive an error becausebasewas already provided. You can make your code type safe by changing the definition offooto the following, which type checks without error:Dreamsorcerer commentedon Feb 25, 2023
Right, but then we lose all typing for arguments.
My actual use case is:
Would be nice not to lose typing on the kwargs (and I get an explicit Any error too).
Dreamsorcerer commentedon Feb 25, 2023
Although, that's interesting that I can do
init_app(EncryptedCookieStorage), which then type checks, but would fail at runtime...So, actually, maybe the problem is that it allows a subclass of the abstract type to require an argument which the abstract class does not require...
https://github.com/aio-libs/aiohttp-session/blob/master/aiohttp_session/cookie_storage.py#L18
NeilGirdhar commentedon Feb 25, 2023
In Python, constructors don't obey LSP. That's why you shouldn't annotate the function with
type[...]: there's no way to know how with which parameters to construct it. You should annotate withCallable.You can type the arguments by providing them to
Callableor by creating a protocol.Add support for functools.partial
Add support for functools.partial
Add support for functools.partial (#16939)
chore: since python/mypy#1484 has been addressed we can now use parti…