Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

mypy complaining about calls to a datetime factory#1961

Unanswered
cjw296 asked this question inQ&A
Discussion options

Hi All,

Is this a bug in mypy? As best I (and a bunch of unit tests!) can tell, this code is good:

fromdatetimeimportdatetimedefadd(cls:type[datetime],instance:datetime|int|None=None,/,*args:int,**kw:int,)->datetime:ifisinstance(instance,datetime):passelifisinstance(instance,int):instance=cls(instance,*args,**kw)else:instance=cls(*args,**kw)returninstance

...but mypy (1.15.0 runnong on Python 3.12.1) complains with:

$ mypy reproducer.py reproducer.py:14: error: Argument 2 to "datetime" has incompatible type "*tuple[int, ...]"; expected "tzinfo | None"  [arg-type]reproducer.py:14: error: Argument 3 to "datetime" has incompatible type "**dict[str, int]"; expected "tzinfo | None"  [arg-type]reproducer.py:16: error: Argument 1 to "datetime" has incompatible type "*tuple[int, ...]"; expected "tzinfo | None"  [arg-type]reproducer.py:16: error: Argument 2 to "datetime" has incompatible type "**dict[str, int]"; expected "tzinfo | None"  [arg-type]Found 4 errors in 1 file (checked 1 source file)
You must be logged in to vote

Replies: 2 comments 2 replies

Comment options

carljm
Apr 6, 2025
Collaborator

Mypy is accurately telling you that the datetime constructor has an argument (tzinfo) which accepts either atzinfo instance orNone, and you can't pass anint to that argument, but your code might.

I would say it feels slightly arbitrary what the type checker chooses to complain about here, in the sense that this code is much more broadly type-unsafe than that: any invalid keyword name provided to this wrapper, or too many positional arguments, will also result in aTypeError from the datetime constructor call, and the type checker provides no help in preventing that. But type checkers choose to assume that you are you are taking it upon yourself to ensure only the correct argument names (and/or number of positional arguments) are passed when you use*args and**kwargs like this, but it does still take on the job of alerting you that you might be passing the wrong argument type for some arguments.

This might be a good use case forParamSpec? You might be able to actually teach the type checker to know what are the valid arguments to provide toadd.

You must be logged in to vote
2 replies
@cjw296
Comment options

Mypy is accurately telling you that the datetime constructor has an argument (tzinfo) which accepts either a tzinfo instance or None, and you can't pass an int to that argument, but your code might.

I'd argue about accurately, or certainly "helpfully", as this specific error message didn't communicate to what your text cleared up:

reproducer.py:14: error: Argument 2 to "datetime" has incompatible type "*tuple[int, ...]"; expected "tzinfo | None"  [arg-type]

This would have been easier for me to understanding:

reproducer.py:14: error: Argument 2 to "datetime" has incompatible type "*tuple[int, ...]"; expected "*tuple[int | tzinfo | None, ...]"  [arg-type]

I guess we're also running into limitations of *args typing: there's no way to say "this may only be up to this length". What's annoying is that even with added overload definitions, mypy still doesn't infer that this problem can't be hit:

fromdatetimeimportdatetime,tzinfofromtypingimportoverload@overloaddefadd(cls:type[datetime],year:int,month:int,day:int,hour:int=0,minute:int=0,second:int=0,microsecond:int=0,tzinfo:tzinfo|None=None,)->datetime:    ...@overloaddefadd(cls:type[datetime],instance:datetime,/,)->datetime:    ...defadd(cls:type[datetime],instance:datetime|int|None=None,/,*args:int,tzinfo:tzinfo|None=None,**kw:int,)->datetime:ifisinstance(instance,datetime):passelifisinstance(instance,int):instance=cls(instance,*args,tzinfo=tzinfo,**kw)else:instance=cls(*args,tzinfo=tzinfo,**kw)returninstance

...in fact, more errors pop up :-(

$ mypy reproducer.py reproducer.py:29: error: Overloaded function implementation does not accept all possible arguments of signature 1  [misc]reproducer.py:40: error: "datetime" gets multiple values for keyword argument "tzinfo"  [misc]reproducer.py:40: error: Argument 2 to "datetime" has incompatible type "*tuple[int, ...]"; expected "tzinfo | None"  [arg-type]reproducer.py:40: error: Argument 4 to "datetime" has incompatible type "**dict[str, int]"; expected "tzinfo | None"  [arg-type]reproducer.py:42: error: "datetime" gets multiple values for keyword argument "tzinfo"  [misc]reproducer.py:42: error: Argument 1 to "datetime" has incompatible type "*tuple[int, ...]"; expected "tzinfo | None"  [arg-type]reproducer.py:42: error: Argument 3 to "datetime" has incompatible type "**dict[str, int]"; expected "tzinfo | None"  [arg-type]Found 7 errors in 1 file (checked 1 source file)

If you could show me how to useParamSpec in this situation, I'd be very grateful!

@carljm
Comment options

carljmApr 7, 2025
Collaborator

Quibbles with mypy error message wording should go to the mypy bug tracker, not here.

This is the closest I can get withParamSpec on a quick try:

fromdatetimeimportdatetimefromtypingimportCallabledefadd[**P](cls:Callable[P,datetime],instance:datetime|int|None=None,/,*args:P.args,**kw:P.kwargs,)->datetime:ifisinstance(instance,datetime):passelifisinstance(instance,int):instance=cls(instance,*args,**kw)else:instance=cls(*args,**kw)returninstance

It requires changing thecls argument to be any callable that returns a datetime, rather thantype[datetime] specifically. Personally I think this is entirely an improvement: it makes the function more flexible for callers, and it's better because callability oftype is unsafe to begin with (because Liskov is not enforced for__init__ or__new__), so using aCallable annotation instead of atype annotation will give correct behavior, instead of just silently being unsound, if someone passes a subclass ofdatetime with a different constructor signature.

But it can't handle the call with prependedinstance argument. This call can be made OK by usingCallable[typing.Concatenate[int, P], datetime] instead, but then the second call isn't happy. I don't see a way to make that level of dynamism work with the available typing features. I think your best bet might be to use overloads with ParamSpec to achieve really goodexternal typing for the function, but you're not going to get good typing of the internal function body that way: typing of the function body doesn't understand its external overloads at all. This is probably what I'd go with for this case:

fromdatetimeimportdatetimefromtypingimportCallable,Concatenate,overload@overloaddefadd[**P](cls:Callable[P,datetime],instance:datetime|None=None,/,*args:P.args,**kwargs:P.kwargs,)->datetime: ...@overloaddefadd[**P](cls:Callable[Concatenate[int,P],datetime],instance:int,/,*args:P.args,**kwargs:P.kwargs,)->datetime: ...defadd(cls:Callable[...,datetime],instance:datetime|int|None=None,/,*args,**kw,)->datetime:ifisinstance(instance,datetime):passelifisinstance(instance,int):instance=cls(instance,*args,**kw)else:instance=cls(*args,**kw)returninstance
Comment options

See#2040 for newer discussion on this.

You must be logged in to vote
0 replies
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Q&A
Labels
None yet
2 participants
@cjw296@carljm

[8]ページ先頭

©2009-2025 Movatter.jp