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
Should we change PEP 484 to disable implicit Optional when default = None? #275
This was intended as saving some typing in a common case, but I've received strong feedback from some quarters that this is not consistent and a bad idea. There are other places where None is allowed but none of them automatically add Optional (to the contrary).
So far it hasn't mattered much for mypy users because mypy doesn't have Optional support, but that's soon going to change (the --strict-optional flag is becoming more reliable) so if we're going to change this, now would be a good time. Thoughts? If we don't change this soon it will probably be too late.
I'm for this change. If I recall correctly, this is the only place where the PEP suggests typecheckers understand types to be something other than what's written as the type, so I think this change would be a nice simplification.
I've also started to notice this cause a bit of user confusion. See python/typeshed#500, for example, for all the arguments to builtin functions that were accidentally marked Optional due to this.
I have seen some confusions between optional arguments and optional types, and the convention in question amplifies such confusions. Therefore I am in favour of removing it.
OK, let's bring this up on python-dev once Python 3.6 beta 1 is released
(i.e. after next weekend).
On Sat, Sep 3, 2016 at 7:32 AM, Ivan Levkivskyi [email protected]
wrote:
I have seen some confusions between optional arguments and optional types,
and the convention in question amplifies such confusions. Therefore I am in
favour of removing it.
IMO most of the confusion comes from the name being wrong. An argument is «optional» when the parameter has a default in python. An «optional» type is a type that you can choose to add or not (as in gradual typing). If we had Nullable[T] instead of Optional many of these confusions wold go away (and it's the name used mostly everywhere else, other than Maybe T in Haskell).
Other than that, there is an inconsistency that may require a change (I just wanted to clarify that the confusions shouldn't be part of the argument)
Reacted by Sebastian Rittau, Max Kühn, Yunhui YZ Zhang, Edward Knight, 5j9, Max Marrone, wxgeo, Robert, Xavier Roynard and Antti Nilakari
@dmoisset: I agree that Nullable or Nonable (1 char shorter) or Maybe (3 chars shorter) would be better solutions than Optional.
On the other hand, even the short Maybeis cumbersome. Nonable parameters are a frequent case and I believe the =None is sufficiently explicit to warrant introducing an additional PEP rule to cope with it, even if that rule has the character of an exception.
I like that the Optional[] can be omitted if a None default is provided. It is less typing, reads naturally (to me) and is unambiguous.
If users want to use Optional they can since Optional[Optional[T]] == Optional[T].
As syntactic sugar goes, it seems quite sweet :)
OOI, which quarters?
Reacted by Sebastian Rittau, Max Kühn, Mateen Ulhaq, Bar Harel, Chris Withers, wxgeo, Aoran Hu and Antti Nilakari
I haven't had enough experience with using mypy strict optional checking to decide whether the extra verbosity would become awkward. Maybe we should wait until we have a substantial codebase that can be type checked using strict optional checking.
Strict optional checking means that mypy treats None as a regular type, and mypy enforces that you add guards like if x: foo(x + 1) for values with an Optional[] type. Without strict optional checking, None is implicitly compatible with every type, kind of like null in Java, and an Optional[X] type is equivalent to just X. It has nothing to do with syntax.
The reason for adopting the syntactic sugar was the worry that otherwise type annotations would be too verbose and thus maybe also harder to read. I think that the main arguments for not having the syntactic sugar are consistency and conceptual simplicity.
If we have a codebase that uses Optional[] consistently, which can be enforced by the strict optional mode of mypy, we could evaluate, at least for that codebase, whether the arguments hold water. We can get a realistic of estimate of the actual verbosity of annotating code with or without the syntactic sugar. We can perhaps also get a better understanding of whether the inconsistency is an actual problem for people who are working on the code.
It may all boil down to personal preference, but having some empirical data could be useful for the discussion. Also, it doesn't seem to me that this is an urgent thing to resolve, as this doesn't require major typing implementation changes (though get_type_hints may be affected) and it's going to be easy for a static checker to support both modes via a configuration option. I don't see a need to rush the decision, and we can first wait for tools that can perform useful "strict optional" checking to be built.
@ilevkivskyi There's still the question about annotating instance variables with mutable types in the class body in pre-3.6 code. This is what mypy users can write today:
class A:
x = None # type: List[int] # Can't use x = [] (list is mutable) and don't want Optional
def __init__(self) -> None:
self.x = []
This is a common use case and I think we need to have a reasonable way to expressing this without PEP 526 syntax.
I'd say it's not desirable to put a default value if you consider that default value is not an acceptable value. In this case, just don't put a default value. If you don't pass anything, mypy will tell you and you'll get a type error, and if you explicitely pass None, mypy will tell you it's wrong.
deffoo(p: int):
# in the function body, don't try to defend against p being not an int
...
# None is not one of possible values, func1 can't be called with explicit `param = None`deffunc1(param: Class=None):
param=paramorClass("somethin")
# None is one of the possible values, funct2 can be called with explicit `param = None`deffunc2(param: Optional[Class] =None):
pass# None is one of the possible values, funct3 requires param and can be called with explicit `param = None`deffunc3(param: Optional[Class]):
pass# None is one of the possible values, funct3 can be called with `param = None`deffunc4(param: Optional[Class] =obj):
pass
i.e. default values has nothing to do with type annotation i.e. public contract of a function
Not sure I get your point, and I don't agree with "default values has nothing to do with type annotation":
This function has wrong annotations which we can tell BECAUSE the annotations and the default value are clashing:
deffunc(param: int="hello"):
pass
I was answering to:
What if I'm annotating function that does not accept None but has it as a marker of missed argument? E.g.:
deffoo(p: int=None):
ifpisNone:
print("p not specified")
and I understood "does not accept None" as "if you call the function with None (or without an argument), the call is going to be rejected. And I stand by my (... 2-year-old-)opinion: if a function is written to fail if an argument is missing, it's best to not have that argument have a default value.
If you want a default value and you want to be able to detect that the default value was used, either use : Optional[...] = None if None doesn't already have another meaning or do something like:
Instead of treating this as a special case, it could also be understood as implicit type widening, where the default value is allowed to widen the type, e.g. from str to str | None. This works only with type inference, but PEP 484 does recommend:
Type checkers are expected to attempt to infer as much information as necessary.
So arguably, it's not really a special case, but a general policy of widening the type based on a default when the typing tools has inferred the type.
This would also work for the case of a marker object, i.e. missing in @ewjoachim's example.
While I can see some strange pitfalls such as a: int = "foo" this should perhaps not win over the arguably clean syntax offered by a: str = None (assuming that the general policy of implicit type widening would get adopted).
Activity
ddfisher commentedon Sep 3, 2016
I'm for this change. If I recall correctly, this is the only place where the PEP suggests typecheckers understand types to be something other than what's written as the type, so I think this change would be a nice simplification.
I've also started to notice this cause a bit of user confusion. See python/typeshed#500, for example, for all the arguments to builtin functions that were accidentally marked Optional due to this.
ilevkivskyi commentedon Sep 3, 2016
I have seen some confusions between optional arguments and optional types, and the convention in question amplifies such confusions. Therefore I am in favour of removing it.
gvanrossum commentedon Sep 3, 2016
OK, let's bring this up on python-dev once Python 3.6 beta 1 is released
(i.e. after next weekend).
On Sat, Sep 3, 2016 at 7:32 AM, Ivan Levkivskyi [email protected]
wrote:
--Guido van Rossum (python.org/~guido)
dmoisset commentedon Sep 5, 2016
IMO most of the confusion comes from the name being wrong. An argument is «optional» when the parameter has a default in python. An «optional» type is a type that you can choose to add or not (as in gradual typing). If we had
Nullable[T]instead ofOptionalmany of these confusions wold go away (and it's the name used mostly everywhere else, other thanMaybe Tin Haskell).Other than that, there is an inconsistency that may require a change (I just wanted to clarify that the confusions shouldn't be part of the argument)
prechelt commentedon Sep 5, 2016
@dmoisset: I agree that
NullableorNonable(1 char shorter) orMaybe(3 chars shorter) would be better solutions thanOptional.On the other hand, even the short
Maybeis cumbersome. Nonable parameters are a frequent case and I believe the=Noneis sufficiently explicit to warrant introducing an additional PEP rule to cope with it, even if that rule has the character of an exception.markshannon commentedon Sep 5, 2016
I like that the
Optional[]can be omitted if aNonedefault is provided. It is less typing, reads naturally (to me) and is unambiguous.If users want to use
Optionalthey can sinceOptional[Optional[T]] == Optional[T].As syntactic sugar goes, it seems quite sweet :)
OOI, which quarters?
JukkaL commentedon Sep 5, 2016
I haven't had enough experience with using mypy strict optional checking to decide whether the extra verbosity would become awkward. Maybe we should wait until we have a substantial codebase that can be type checked using strict optional checking.
markshannon commentedon Sep 6, 2016
Why do you use the word "strict"? That suggests that there is something "loose" about the syntactic sugar. There isn't; it is precisely defined.
JukkaL commentedon Sep 6, 2016
Strict optional checking means that mypy treats
Noneas a regular type, and mypy enforces that you add guards likeif x: foo(x + 1)for values with anOptional[]type. Without strict optional checking,Noneis implicitly compatible with every type, kind of likenullin Java, and anOptional[X]type is equivalent to justX. It has nothing to do with syntax.markshannon commentedon Sep 6, 2016
So how is it relevant to default values for parameters making the type
Optional?JukkaL commentedon Sep 6, 2016
The reason for adopting the syntactic sugar was the worry that otherwise type annotations would be too verbose and thus maybe also harder to read. I think that the main arguments for not having the syntactic sugar are consistency and conceptual simplicity.
If we have a codebase that uses
Optional[]consistently, which can be enforced by the strict optional mode of mypy, we could evaluate, at least for that codebase, whether the arguments hold water. We can get a realistic of estimate of the actual verbosity of annotating code with or without the syntactic sugar. We can perhaps also get a better understanding of whether the inconsistency is an actual problem for people who are working on the code.It may all boil down to personal preference, but having some empirical data could be useful for the discussion. Also, it doesn't seem to me that this is an urgent thing to resolve, as this doesn't require major
typingimplementation changes (thoughget_type_hintsmay be affected) and it's going to be easy for a static checker to support both modes via a configuration option. I don't see a need to rush the decision, and we can first wait for tools that can perform useful "strict optional" checking to be built.markshannon commentedon Sep 6, 2016
Whether it is easier to read is, as you say, personal preference, which is rarely influenced by empirical evidence 😄
Happy to wait and see what you come up with.
ilevkivskyi commentedon Sep 6, 2016
@JukkaL I ma not sure what kinds of inconsistency you mean, but this one
will go away after PEP 526. I think there it is better to assume implicit
Optionalor not for both of thesebut not separately for each of these.
markshannon commentedon Sep 6, 2016
Ah, that thing. I think that should never have been added. It is just self-contradictory.
JukkaL commentedon Sep 6, 2016
@ilevkivskyi There's still the question about annotating instance variables with mutable types in the class body in pre-3.6 code. This is what mypy users can write today:
This is a common use case and I think we need to have a reasonable way to expressing this without PEP 526 syntax.
33 remaining items
ilevkivskyi commentedon Jul 1, 2018
I assume we will keep a flag in mypy, but will just invert the default. Right?
gvanrossum commentedon Jul 2, 2018
Yes we should do that.
PEP 484: Do not require type checkers to treat a None default special…
gvanrossum commentedon Jul 10, 2018
Closed by python/peps#689.
sproshev commentedon Jan 9, 2019
What if I'm annotating function that does not accept
Nonebut has it as a marker of missed argument? E.g.:Should I use some another value to achieve this? If so, should it have type
intor other one? E.g.:ewjoachim commentedon Jan 9, 2019
I'd say it's not desirable to put a default value if you consider that default value is not an acceptable value. In this case, just don't put a default value. If you don't pass anything, mypy will tell you and you'll get a type error, and if you explicitely pass None, mypy will tell you it's wrong.
sproshev commentedon Jan 11, 2019
What about other cases such as
dataclasses.field? Default value fordefaultanddefault_factoryhas incompatible type. See stub and specification.mypy: Enable no_implicit_optional
PR (#1)
keu commentedon Apr 11, 2021
@ewjoachim there are these different scenarios:
i.e. default values has nothing to do with type annotation i.e. public contract of a function
ewjoachim commentedon Apr 12, 2021
Not sure I get your point, and I don't agree with "default values has nothing to do with type annotation":
This function has wrong annotations which we can tell BECAUSE the annotations and the default value are clashing:
I was answering to:
and I understood "does not accept
None" as "if you call the function with None (or without an argument), the call is going to be rejected. And I stand by my (... 2-year-old-)opinion: if a function is written to fail if an argument is missing, it's best to not have that argument have a default value.If you want a default value and you want to be able to detect that the default value was used, either use
: Optional[...] = Noneif None doesn't already have another meaning or do something like:or
Maybe I missed an important point ?
Fix no-implicit-optional change in mypy/pep484
Chador-safari-web-inspec commentedon Feb 4, 2025
malthe commentedon Sep 26, 2025
Instead of treating this as a special case, it could also be understood as implicit type widening, where the default value is allowed to widen the type, e.g. from
strtostr | None. This works only with type inference, but PEP 484 does recommend:So arguably, it's not really a special case, but a general policy of widening the type based on a default when the typing tools has inferred the type.
This would also work for the case of a marker object, i.e.
missingin @ewjoachim's example.While I can see some strange pitfalls such as
a: int = "foo"this should perhaps not win over the arguably clean syntax offered bya: str = None(assuming that the general policy of implicit type widening would get adopted).