Optional[ ] is not really optional
Given recent usability improvements in type hints (PEPs 585, 604), I thought I'd raise this minor issue which I find relevant as an instructor. Given this signature: def f(a: float, b: Optional[int]) -> float: The `b` parameter is not optional. The caller must always provide an actual argument as an integer or None. To make `b` optional, we must write: def f(a: float, b: Optional[int] = None) -> float: PEP 604 syntax avoids the misleading name `Optional`, which is progress: def f(a: float, b: int | None = None) -> float: But now another minor issue is highlighted: the repetition of None. That's unfortunate given that almost all uses of None in parameters are precisely as default values for optional parameters—that the caller doesn't need to provide. I wonder if the authors of the typing PEPs considered these issues, or if there are ideas to fix them. I don't have a proposal at this time. I wanted to start the conversation to see if we can find a fix, or at least learn to live with the current status—which is not bad, but also not excellent in terms of readability. Cheers, Luciano -- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg
Good question.
In early revisions of PEP 484 you could write
def foo(a: int = None) -> ...
and it would imply that the type of 'a' was really Optional[int].
After several years of practice we decided to *remove* this automatic
optionalization because it was widely misunderstood and/or confusing for
users. For the whole discussion, see
https://github.com/python/typing/issues/275. (Also, PEP 484 still mentions
it -- "A past version of this PEP allowed ...")
In fact, mypy still defaults to this behavior, and you have to request the
new, stricter PEP 484 behavior with `--no-implied-optional`.
Maybe we could satisfy both sides if the notation for Optional[X] was truly
short, like ?X or -X. But that has its own issues -- none of the existing
unary operators (+X, -X, ~X) really satisfies, and ?X would require a
syntax change (and would forever fix the meaning of one of the last ASCII
punctuation characters that has no meaning in Python -- $ being the other
one).
Finally, I wish the whole term "optional" would go away, because of the
endless confusion with optional parameters and optional dict keys -- in
these contexts the word is widely used to mean "may or may not be present",
without implying that the type may include None.
But I digress...
--Guido
On Wed, May 6, 2020 at 8:47 AM Luciano Ramalho
Given recent usability improvements in type hints (PEPs 585, 604), I thought I'd raise this minor issue which I find relevant as an instructor.
Given this signature:
def f(a: float, b: Optional[int]) -> float:
The `b` parameter is not optional. The caller must always provide an actual argument as an integer or None. To make `b` optional, we must write:
def f(a: float, b: Optional[int] = None) -> float:
PEP 604 syntax avoids the misleading name `Optional`, which is progress:
def f(a: float, b: int | None = None) -> float:
But now another minor issue is highlighted: the repetition of None. That's unfortunate given that almost all uses of None in parameters are precisely as default values for optional parameters—that the caller doesn't need to provide.
I wonder if the authors of the typing PEPs considered these issues, or if there are ideas to fix them.
I don't have a proposal at this time. I wanted to start the conversation to see if we can find a fix, or at least learn to live with the current status—which is not bad, but also not excellent in terms of readability.
Cheers,
Luciano
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...
On Wed, May 6, 2020 at 1:03 PM Guido van Rossum
Good question.
In early revisions of PEP 484 you could write
def foo(a: int = None) -> ...
and it would imply that the type of 'a' was really Optional[int].
After several years of practice we decided to *remove* this automatic optionalization because it was widely misunderstood and/or confusing for users. For the whole discussion, see https://github.com/python/typing/issues/275. (Also, PEP 484 still mentions it -- "A past version of this PEP allowed ...")
In fact, mypy still defaults to this behavior, and you have to request the new, stricter PEP 484 behavior with `--no-implied-optional`.
Thanks for these pointers to the history of the issue, Guido.
Maybe we could satisfy both sides if the notation for Optional[X] was truly short, like ?X or -X. But that has its own issues -- none of the existing unary operators (+X, -X, ~X) really satisfies, and ?X would require a syntax change (and would forever fix the meaning of one of the last ASCII punctuation characters that has no meaning in Python -- $ being the other one).
I don't think ~X is too bad, if we want to preserve ? for future uses. I also like !X because ! suggests "beware!"—although it would require deeper changes in the language. The past proposals I've seen using ? have to do with handling None or missing attributes, so maybe ?X is the way to go after all—if those needs can be reconciled. Given that the status quo is workable, maybe we can come back to this issue when another proposal for using ? with attributes comes around, to try and solve both problems in a way that looks consistent.
Finally, I wish the whole term "optional" would go away, because of the endless confusion with optional parameters and optional dict keys -- in these contexts the word is widely used to mean "may or may not be present", without implying that the type may include None.
I'd prefer Maybe[x], but I guess that ship has sailed...
Cheers,
Luciano
On Wed, May 6, 2020 at 1:03 PM Guido van Rossum
Good question.
In early revisions of PEP 484 you could write
def foo(a: int = None) -> ...
and it would imply that the type of 'a' was really Optional[int].
After several years of practice we decided to *remove* this automatic optionalization because it was widely misunderstood and/or confusing for users. For the whole discussion, see https://github.com/python/typing/issues/275. (Also, PEP 484 still mentions it -- "A past version of this PEP allowed ...")
In fact, mypy still defaults to this behavior, and you have to request the new, stricter PEP 484 behavior with `--no-implied-optional`.
Maybe we could satisfy both sides if the notation for Optional[X] was truly short, like ?X or -X. But that has its own issues -- none of the existing unary operators (+X, -X, ~X) really satisfies, and ?X would require a syntax change (and would forever fix the meaning of one of the last ASCII punctuation characters that has no meaning in Python -- $ being the other one).
Finally, I wish the whole term "optional" would go away, because of the endless confusion with optional parameters and optional dict keys -- in these contexts the word is widely used to mean "may or may not be present", without implying that the type may include None.
But I digress...
--Guido
On Wed, May 6, 2020 at 8:47 AM Luciano Ramalho
wrote: Given recent usability improvements in type hints (PEPs 585, 604), I thought I'd raise this minor issue which I find relevant as an instructor.
Given this signature:
def f(a: float, b: Optional[int]) -> float:
The `b` parameter is not optional. The caller must always provide an actual argument as an integer or None. To make `b` optional, we must write:
def f(a: float, b: Optional[int] = None) -> float:
PEP 604 syntax avoids the misleading name `Optional`, which is progress:
def f(a: float, b: int | None = None) -> float:
But now another minor issue is highlighted: the repetition of None. That's unfortunate given that almost all uses of None in parameters are precisely as default values for optional parameters—that the caller doesn't need to provide.
I wonder if the authors of the typing PEPs considered these issues, or if there are ideas to fix them.
I don't have a proposal at this time. I wanted to start the conversation to see if we can find a fix, or at least learn to live with the current status—which is not bad, but also not excellent in terms of readability.
Cheers,
Luciano
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: guido@python.org
-- --Guido van Rossum (python.org/~guido) Pronouns: he/him (why is my pronoun here?)
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg
I've always understood Optional to mean: can be None or the type specified,
not that it's an optional argument to a function. These really aren't
related. The function signature having a default argument value makes the
argument optional, not the typing system, especially since the typing
system is not supposed to influence runtime. The newer syntax makes this
clear.
On Wed, May 6, 2020, 11:47 Luciano Ramalho
Given recent usability improvements in type hints (PEPs 585, 604), I thought I'd raise this minor issue which I find relevant as an instructor.
Given this signature:
def f(a: float, b: Optional[int]) -> float:
The `b` parameter is not optional. The caller must always provide an actual argument as an integer or None. To make `b` optional, we must write:
def f(a: float, b: Optional[int] = None) -> float:
PEP 604 syntax avoids the misleading name `Optional`, which is progress:
def f(a: float, b: int | None = None) -> float:
But now another minor issue is highlighted: the repetition of None. That's unfortunate given that almost all uses of None in parameters are precisely as default values for optional parameters—that the caller doesn't need to provide.
I wonder if the authors of the typing PEPs considered these issues, or if there are ideas to fix them.
I don't have a proposal at this time. I wanted to start the conversation to see if we can find a fix, or at least learn to live with the current status—which is not bad, but also not excellent in terms of readability.
Cheers,
Luciano
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: audvare@gmail.com
In TypeScript "optional" works fine for naming the addition of null/etc. --
it isn't an issue because for JS, all arguments are optional if "undefined"
is an allowed value and so TS requires all method arguments when
"undefined" is not allowed. In TS, ? is shorthand for adding both null and
undefined and allowing undefined is thus what makes it optional, no need to
explicitly define a default value. This is described here:
https://www.typescriptlang.org/docs/handbook/functions.html#optional-and-def...
In Python, the other way we could work around this is if Optional[int] were
given the additional behavior of assigning the default value of "None"
implicitly if no other default value were specified:
def f(a: float, b: Optional[int]) -> float:
If "?" appended to a variable name could be defined similarly to
Optional[int] as a shorthand for = None as well, you'd have even shorter
syntax to express the intent of an "optional argument":
def f(a: float, b?: int) -> float:
and it would still be "optional" but with less typing...
Louis.
On Wed, May 6, 2020 at 12:40 PM Andrew Udvare
I've always understood Optional to mean: can be None or the type specified, not that it's an optional argument to a function. These really aren't related. The function signature having a default argument value makes the argument optional, not the typing system, especially since the typing system is not supposed to influence runtime. The newer syntax makes this clear.
On Wed, May 6, 2020, 11:47 Luciano Ramalho
wrote: Given recent usability improvements in type hints (PEPs 585, 604), I thought I'd raise this minor issue which I find relevant as an instructor.
Given this signature:
def f(a: float, b: Optional[int]) -> float:
The `b` parameter is not optional. The caller must always provide an actual argument as an integer or None. To make `b` optional, we must write:
def f(a: float, b: Optional[int] = None) -> float:
PEP 604 syntax avoids the misleading name `Optional`, which is progress:
def f(a: float, b: int | None = None) -> float:
But now another minor issue is highlighted: the repetition of None. That's unfortunate given that almost all uses of None in parameters are precisely as default values for optional parameters—that the caller doesn't need to provide.
I wonder if the authors of the typing PEPs considered these issues, or if there are ideas to fix them.
I don't have a proposal at this time. I wanted to start the conversation to see if we can find a fix, or at least learn to live with the current status—which is not bad, but also not excellent in terms of readability.
Cheers,
Luciano
-- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: audvare@gmail.com
_______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: louisstamour@gmail.com
On 2020-05-06, at 13:20, Louis St-Amour
wrote: In TypeScript "optional" works fine for naming the addition of null/etc. -- it isn't an issue because for JS, all arguments are optional if "undefined" is an allowed value and so TS requires all method arguments when "undefined" is not allowed. In TS, ? is shorthand for adding both null and undefined and allowing undefined is thus what makes it optional, no need to explicitly define a default value. This is described here: https://www.typescriptlang.org/docs/handbook/functions.html#optional-and-def...
In Python, the other way we could work around this is if Optional[int] were given the additional behavior of assigning the default value of "None" implicitly if no other default value were specified:
def f(a: float, b: Optional[int]) -> float:
If "?" appended to a variable name could be defined similarly to Optional[int] as a shorthand for = None as well, you'd have even shorter syntax to express the intent of an "optional argument":
def f(a: float, b?: int) -> float:
and it would still be "optional" but with less typing...
I like and use TypeScript quite a bit... But I think we should avoid this because Python is more about being explicit than implicit (i.e. self argument in instance methods). So having to type =None may be less convenient but it makes it clear what the default value is for an optional argument. Anything like having a question mark would be changing the language quite a bit. It would probably be preferred to introduce a new (no-op) keyword like 'optional' or 'opt', but I don't think that's a very good idea either because then there's less available words for identifiers, or it could affect BC. Another thing is that Python functions have a set number of arguments based on the signature whereas JavaScript functions can accept any amount (TypeScript can stop this at compile time). It would be a large change if this were introduced: def f(a: Optional[int]) # f() or f(1) are both valid What should the default value for a be? Should it be defined and be set to None or it should it be undefined? In Python (unlike JavaScript) that would mean any mention of 'a' would be invalid, raising NameError. Which would have major implications on how to structure the function. Andrew
On Wed, May 6, 2020 at 6:20 PM Louis St-Amour
In TypeScript "optional" works fine for naming the addition of null/etc. -- it isn't an issue because for JS, all arguments are optional if "undefined" is an allowed value and so TS requires all method arguments when "undefined" is not allowed. In TS, ? is shorthand for adding both null and undefined and allowing undefined is thus what makes it optional, no need to explicitly define a default value. This is described here: https://www.typescriptlang.org/docs/handbook/functions.html#optional-and-def...
Thanks for bringing TypeScript into the conversation. I believe we can learn from it, but there are differences as you point out.
In Python, the other way we could work around this is if Optional[int] were given the additional behavior of assigning the default value of "None" implicitly if no other default value were specified:
def f(a: float, b: Optional[int]) -> float:
I agree this would be good, but it breaks the current rule that type annotations should not have effects at runtime (although there is a precedent in dataclasses, where the ClassVar "type" causes a class variable to be created, while other type hinted vars disappear at runtime unless they are initialized).
If "?" appended to a variable name could be defined similarly to Optional[int] as a shorthand for = None as well, you'd have even shorter syntax to express the intent of an "optional argument":
def f(a: float, b?: int) -> float:
and it would still be "optional" but with less typing...
Yep, I like that. From Guido's answer, I gather there is a preference to use a unary operator on the type, to reduce the impact on the parser, so that would be `b: ?int`. And if we want to prevent the type hint from having a runtime effect, it would be `b: ?int = None`. Still, better than the current situation, IMHO. For TypeScript, they created a transpiler so they could add syntax at will. We don't have that luxury in Python. Cheers, Luciano -- Luciano Ramalho | Author of Fluent Python (O'Reilly, 2015) | http://shop.oreilly.com/product/0636920032519.do | Technical Principal at ThoughtWorks | Twitter: @ramalhoorg
participants (4)
-
Andrew Udvare
-
Guido van Rossum
-
Louis St-Amour
-
Luciano Ramalho