Open karolyi opened 1 month ago
Hey @erictraut,
this bug is not fixed. See:
Code sample in pyright playground
from typing import Literal x = '1' y = '2' xy = Literal[(x, y)] reveal_type(xy) # should be Literal['1', '2'], it isn't z = 1 xyz = Literal[(x, y, z)] reveal_type(xyz) # should be Literal['1', '2', 1], it isn't foo = Literal[*(x, y, z)] reveal_type(foo) # Should be Literal['1', '2', 1] from 3.11 on, it isn't
what's your usecase? using a value as a type isn't valid (maybe something that's Final
could be considered valid 🤔)
why not use types here?
from typing import Literal
type x = Literal['1']
type y = Literal['2']
type xy = Literal[x, y]
if we allow Final
:
x: Final = 1
y: Final = 2
type XY = Literal[x, y] # i think this could be fine
values = (1, 2, 3, 4)
type Value = Literal[*values] # i think this could be fine
this actually look extremely useful imo
@DetachHead what's your opinion?
Expanding on why this is important, with a more precise example:
Python 3.11.9 (main, Aug 1 2024, 12:59:41) [GCC 14.1.1 20240522] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from typing import Literal >>> from inspect import signature, Parameter >>> >>> valid_values = list(signature(Parameter).parameters) >>> >>> ParamAttribute = Literal[*valid_values] >>> assert ParamAttribute == Literal['name', 'kind', 'default', 'annotation'] >>> ParamAttribute typing.Literal['name', 'kind', 'default', 'annotation']
this wouldn't work, because statically all type information of the signature is lost in signature
, to achieve this we would need some mechanism to catpure the names/types of the parameters within the type system
My usecase is django choices, e.g.:
from typing import Literal
from ktools.django.utils.translation import gettext_safelazy as _
BILLING_BY_WIRE_TRANSFER = 'wire-transfer'
BILLING_BY_COLLECTION = 'sepa-collection'
BILLING_TYPES = (
(BILLING_BY_WIRE_TRANSFER, _('Wire transfer')),
(BILLING_BY_COLLECTION, _('SEPA collection')),
)
BILLING_TYPES_DICT = dict(BILLING_TYPES)
BillingTypesType = Literal['wire-transfer', 'sepa-collection']
It would be great to have BillingTypesType
deducted from either BILLING_TYPES_DICT.keys()
, or BILLING_TYPES
. But I'd also be fine with
BillingTypesType = Literal[(BILLING_BY_WIRE_TRANSFER, BILLING_BY_COLLECTION)]
None of these are available now, and handling form/model choices would be better supported with types that support this.
because those are uppercase, they would be pseudo Final
, i can't imagine why this couldn't be supported. well have to get signoff from the design committee before we can start work on it though
Do what you have to do, I'm just spinning ideas here :)
If it gets rejected, it's fine either way. I'll be only more happy when it somehow goes through.
There is merit to what traut says too. I'm just thinking, why not if python already depicts it that way?
Traut is wrong here, when he says:
You are conflating values and types. At runtime, the interpreter evaluates the value of an expression. A static type checker evaluates the type of an expression. ... Remember, static type checkers don't actually run your code.
he is correct that a type checker does not execute your code, but incorrect that it means that it rules out as being usable in a type position. for example, here, we use the value cls
in a type position, because the semantics determine that we know what the value of cls
will always be (it will always be an instance of type[Self]
, so using it in a type position would be the same as writing Self
)
class A:
@classmethod
def f(cls):
a: cls = cls()
as long as the type checker has the necessary static information about a value, it should be able to use the value in a type position
the fact that you can spread a tuple into Literal
at runtime is a consequence of the terrible design decision to not develop any typing syntax, which meant we have all these stupid classes that exist at runtime that don't make any sense being a class (eg. Generic
, ABC
, Union
, Literal
, etc). so im a bit skeptical of supporting stuff like this because it seems that the runtime machinery for this stuff is so poorly thought out that it changes all the time (for example i think the runtime representation of the new union syntax (|
) is completely different to the old Union
type for some reason)
lets look at how typescript supports the same use case:
const x = 1
const y = 2
type XY = typeof x | typeof y
or if you have a tuple of values, you can get a union of all of its values using [number]
index access:
const values = [1, 2, 3] as const
type Value = (typeof values)[number] // 1 | 2 | 3
ideally Literal
shouldn't even exist at all and you should instead be able to just use a separate "type-realm" syntax for stuff like this like you can in typescript.
the other concern i have (which i guess isn't entirely related to your idea) is how confusing tuples are inside square brackets:
>>> Literal[*(1,2)].__args__
(1, 2)
>>> Literal[(1,2)].__args__
(1, 2)
>>> Literal[1,2].__args__
(1, 2)
you'd intuitively think that because Literal[*(1,2)]
means the same as Literal[1, 2]
(as in the value could either be 1
or 2
), then Literal[(1, 2)]
means the value can only be the tuple (1, 2)
. but at runtime as far as the Literal
class is concerned they are all the same thing. (related: #5)
Hey,
what's your stance on this issue?