Simple syntax for Literal

Hi typing-sig, not sure if this is the correct place to post this. If it isn't, I'd be happy to be guided to the correct place. Anyways, here goes: I'm using Literal a lot for constants/magic numbers, both internally and when referring to constants/#defines used in foreign C functions. And I have one major gripe with them: they are unnecessarily verbose because they require writting everything twice. # +++++ CODE BLOCK START +++++ # Example: some constants from winuser.h used in windll.user32.MapVirtualKeyW MAPVK_VK_TO_VSC: Literal[0] = 0 MAPVK_VSC_TO_VK: Literal[1] = 1 MAPVK_VK_TO_CHAR: Literal[2] = 2 MAPVK_VSC_TO_VK_EX: Literal[3] = 3 MAPVK_VK_TO_VSC_EX: Literal[4] = 4 # +++++ CODE BLOCK END +++++ As you can see, every literal needs to be written twice to conform with the type annotation syntax as it currently is. Similar to the type inference for basic Final annotations without [<type>], I'd like to propse allowing the usage of basic Literal annotations without [<value>] if they are annotated and assigned on the same line. So these two lines would both result in MAPVK_VK_TO_VSC having the Literal[0] type hint. # +++++ CODE BLOCK START +++++ MAPVK_VK_TO_VSC: Literal = 0 MAPVK_VK_TO_VSC: Literal[0] = 0 # +++++ CODE BLOCK END +++++ For the implementation detail, I would err on the side of caution and only allow this notation in basic "variable: Literal = value" form. More complex forms like "variable: tuple[Literal, Literal] = [0, 1]" don't strike me as worthwhile enough to consider the additional implementation overhead/complexity. There are already enough things to consider, even with just the basic form. See the following two approaches. Approach A: If a naked Literal annotation is found, type checkers should ideally use the right hand side of the expression and treat it as if it was copied into the Literal[ ] annotation by the user. # +++++ CODE BLOCK START +++++ def get_value() -> Literal[0]: ... var different_literal: Literal[Color.RED] = Color.RED var non_literal: Final[bytes] = b'data' var: Literal = "value" # legal var: Literal["value"] = "value" # equivalent expression var: Literal = get_value() # should be treated as error and flagged by type checkers, even if get_value() returns Literal types. var: Literal[get_value()] = get_value() # equivalent expression, showing why it's an error var: Literal = different_literal # should be treated as error and flagged by type checkers, var: Literal[different_literal] = different_literal # equivalent expression var: Literal = non_literal # should be treated as error and flagged by type checkers, var: Literal[non_literal] = non_literal # equivalent expression # +++++ CODE BLOCK END +++++ This is probably the simpliest to implement, since it requires no inference of the right hand side. A non-literal, even another variable with Literal annotation would simply be treated as illegal. Approach B: Infer the value of the Literal to insert based on the right hand side's annotation. # +++++ CODE BLOCK START +++++ var: Literal = "value" # legal var: Literal["value"] = "value" # equivalent expression var: Literal = get_value() # legal, since the naked Literal would look for the annotation of get_value() and get Literal[0] var: Literal[get_value()] = get_value() # equivalent expression, showing why it's an error var: Literal = different_literal # legal, since the naked Literal will look up different_literal's annotation and get Literal[Color.RED] var: Literal[different_literal] = different_literal # equivalent expression var: Literal = non_literal # not sure about this one, looking at current behavior when assigning a Final to a Literal, mypy would flag it, pyright would treat non_literal as a literal when possible. var: Literal[non_literal] = non_literal # equivalent expression # +++++ CODE BLOCK END +++++ I'd be fine with any approach, since both solve my basic use case of reducing duplicated code. I've scanned through PEP 586 (Literal Types), and I found no mention on why "naked" Literal should be disallowed, indicating to me that it was simply never considered. (Which is not surprising, since most examples in that PEP are using Literal in function argument or return types, where the simple syntax would not apply) Under the point "Adding more concise syntax", it simply mentions skipping on Literal completely is not an option. This proposal still requires the Literal annotation, it simply extends its usage in a minor way that doesn't break grammar. I'd appreciate any feedback on angles that I might have missed while investigating the feasability of this propsal. Best wishes, Jakob

What's difference between your proposed A, var: Literal = 1 vs var: Final = 1? Final when used with literal compatible type infers a type of Literal. So reveal_type(var) on second case is already Literal[1].

As @Mehdi2277 mentioned, using a bare `Final` annotation addresses this use case already. It works fine if the expression on the RHS of the assignment is simple, such as a literal integer value (`1`) or the negation of an integer literal (`-1`). I wouldn't recommend using more complex expressions on the RHS of a `Final` declaration, at least if this is part of a library, because inference results for more complex expressions can differ from one type checker to another.

Thanks for the feedback. I just realized, I may have used Final in the wrong way the whole time. var: Final = 1 # reveals Literal[1]? var: Final[int] = 1 # reveals builtins.int I probably used the explicitly typed version when testing and playing around with Final that I got the wrong idea on how it works. Rereading the mypy docs with hindsight, it is pretty explicit on difference between the two. It seems like intuition led me astray. In that case, yes, Approach A boils down to naked Literal behaving almost the same way as Final. Which degrades this proposal to super low priority and changes the nature of things. This should probably be at most a QoL feature request for the type checker implementations to just add a "Consider using Final" to error messages when it encounters Literal without value. Thank you. ---- ORIGINAL MESSAGE ---- Date: 2022-04-19 22:10:47 UTC+0200 From:eric@traut.com To:typing-sig@python.org Subject: [Typing-sig] Re: Simple syntax for Literal

What's difference between your proposed A, var: Literal = 1 vs var: Final = 1? Final when used with literal compatible type infers a type of Literal. So reveal_type(var) on second case is already Literal[1].

As @Mehdi2277 mentioned, using a bare `Final` annotation addresses this use case already. It works fine if the expression on the RHS of the assignment is simple, such as a literal integer value (`1`) or the negation of an integer literal (`-1`). I wouldn't recommend using more complex expressions on the RHS of a `Final` declaration, at least if this is part of a library, because inference results for more complex expressions can differ from one type checker to another.

Thanks for the feedback. I just realized, I may have used Final in the wrong way the whole time. var: Final = 1 # reveals Literal[1]? var: Final[int] = 1 # reveals builtins.int I probably used the explicitly typed version when testing and playing around with Final that I got the wrong idea on how it works. Rereading the mypy docs with hindsight, it is pretty explicit on difference between the two. It seems like intuition led me astray. In that case, yes, Approach A boils down to naked Literal behaving almost the same way as Final. Which degrades this proposal to super low priority and changes the nature of things. This should probably be at most a QoL feature request for the type checker implementations to just add a "Consider using Final" to error messages when it encounters Literal without value. Thank you. ---- ORIGINAL MESSAGE ---- Date: 2022-04-19 22:10:47 UTC+0200 From:eric@traut.com To:typing-sig@python.org Subject: [Typing-sig] Re: Simple syntax for Literal
participants (4)
-
dev@reggx.eu
-
Eric Traut
-
Mehdi2277
-
ℛℯℊℊ✗