3
3
Literal types
4
4
=============
5
5
6
- .. note ::
7
-
8
- ``Literal `` is an officially supported feature, but is highly experimental
9
- and should be considered to be in alpha stage. It is very likely that future
10
- releases of mypy will modify the behavior of literal types, either by adding
11
- new features or by tuning or removing problematic ones.
12
-
13
6
Literal types let you indicate that an expression is equal to some specific
14
7
primitive value. For example, if we annotate a variable with type ``Literal["foo"] ``,
15
8
mypy will understand that variable is not only of type ``str ``, but is also
@@ -23,8 +16,7 @@ precise type signature for this function using ``Literal[...]`` and overloads:
23
16
24
17
.. code-block :: python
25
18
26
- from typing import overload, Union
27
- from typing_extensions import Literal
19
+ from typing import overload, Union, Literal
28
20
29
21
# The first two overloads use Literal[...] so we can
30
22
# have precise return types:
@@ -53,18 +45,25 @@ precise type signature for this function using ``Literal[...]`` and overloads:
53
45
variable = True
54
46
reveal_type(fetch_data(variable)) # Revealed type is 'Union[bytes, str]'
55
47
48
+ .. note ::
49
+
50
+ The examples in this page import ``Literal `` as well as ``Final `` and
51
+ ``TypedDict `` from the ``typing `` module. These types were added to
52
+ ``typing `` in Python 3.8, but are also available for use in Python 2.7
53
+ and 3.4 - 3.7 via the ``typing_extensions `` package.
54
+
56
55
Parameterizing Literals
57
56
***********************
58
57
59
- Literal types may contain one or more literal bools, ints, strs, and bytes.
60
- However, literal types **cannot ** contain arbitrary expressions:
58
+ Literal types may contain one or more literal bools, ints, strs, bytes, and
59
+ enum values. However, literal types **cannot ** contain arbitrary expressions:
61
60
types like ``Literal[my_string.trim()] ``, ``Literal[x > 3] ``, or ``Literal[3j + 4] ``
62
61
are all illegal.
63
62
64
63
Literals containing two or more values are equivalent to the union of those values.
65
- So, ``Literal[-3, b"foo", True ] `` is equivalent to
66
- ``Union[Literal[-3], Literal[b"foo"], Literal[True ]] ``. This makes writing
67
- more complex types involving literals a little more convenient.
64
+ So, ``Literal[-3, b"foo", MyEnum.A ] `` is equivalent to
65
+ ``Union[Literal[-3], Literal[b"foo"], Literal[MyEnum.A ]] ``. This makes writing more
66
+ complex types involving literals a little more convenient.
68
67
69
68
Literal types may also contain ``None ``. Mypy will treat ``Literal[None] `` as being
70
69
equivalent to just ``None ``. This means that ``Literal[4, None] ``,
@@ -88,9 +87,6 @@ Literals may not contain any other kind of type or expression. This means doing
88
87
``Literal[my_instance] ``, ``Literal[Any] ``, ``Literal[3.14] ``, or
89
88
``Literal[{"foo": 2, "bar": 5}] `` are all illegal.
90
89
91
- Future versions of mypy may relax some of these restrictions. For example, we
92
- plan on adding support for using enum values inside ``Literal[...] `` in an upcoming release.
93
-
94
90
Declaring literal variables
95
91
***************************
96
92
@@ -115,7 +111,7 @@ you can instead change the variable to be ``Final`` (see :ref:`final_attrs`):
115
111
116
112
.. code-block :: python
117
113
118
- from typing_extensions import Final, Literal
114
+ from typing import Final, Literal
119
115
120
116
def expects_literal (x : Literal[19 ]) -> None : pass
121
117
@@ -134,7 +130,7 @@ For example, mypy will type check the above program almost as if it were written
134
130
135
131
.. code-block :: python
136
132
137
- from typing_extensions import Final, Literal
133
+ from typing import Final, Literal
138
134
139
135
def expects_literal (x : Literal[19 ]) -> None : pass
140
136
@@ -151,7 +147,7 @@ For example, compare and contrast what happens when you try appending these type
151
147
152
148
.. code-block :: python
153
149
154
- from typing_extensions import Final, Literal
150
+ from typing import Final, Literal
155
151
156
152
a: Final = 19
157
153
b: Literal[19 ] = 19
@@ -168,6 +164,131 @@ For example, compare and contrast what happens when you try appending these type
168
164
reveal_type(list_of_lits) # Revealed type is 'List[Literal[19]]'
169
165
170
166
167
+ Intelligent indexing
168
+ ********************
169
+
170
+ We can use Literal types to more precisely index into structured heterogeneous
171
+ types such as tuples, NamedTuples, and TypedDicts. This feature is known as
172
+ *intelligent indexing *.
173
+
174
+ For example, when we index into a tuple using some int, the inferred type is
175
+ normally the union of the tuple item types. However, if we want just the type
176
+ corresponding to some particular index, we can use Literal types like so:
177
+
178
+ .. code-block :: python
179
+
180
+ from typing import TypedDict
181
+
182
+ tup = (" foo" , 3.4 )
183
+
184
+ # Indexing with an int literal gives us the exact type for that index
185
+ reveal_type(tup[0 ]) # Revealed type is 'str'
186
+
187
+ # But what if we want the index to be a variable? Normally mypy won't
188
+ # know exactly what the index is and so will return a less precise type:
189
+ int_index = 1
190
+ reveal_type(tup[int_index]) # Revealed type is 'Union[str, float]'
191
+
192
+ # But if we use either Literal types or a Final int, we can gain back
193
+ # the precision we originally had:
194
+ lit_index: Literal[1 ] = 1
195
+ fin_index: Final = 1
196
+ reveal_type(tup[lit_index]) # Revealed type is 'str'
197
+ reveal_type(tup[fin_index]) # Revealed type is 'str'
198
+
199
+ # We can do the same thing with with TypedDict and str keys:
200
+ class MyDict (TypedDict ):
201
+ name: str
202
+ main_id: int
203
+ backup_id: int
204
+
205
+ d: MyDict = {" name" : " Saanvi" , " main_id" : 111 , " backup_id" : 222 }
206
+ name_key: Final = " name"
207
+ reveal_type(d[name_key]) # Revealed type is 'str'
208
+
209
+ # You can also index using unions of literals
210
+ id_key: Literal[" main_id" , " backup_id" ]
211
+ reveal_type(d[id_key]) # Revealed type is 'int'
212
+
213
+ .. _tagged_unions :
214
+
215
+ Tagged unions
216
+ *************
217
+
218
+ When you have a union of types, you can normally discriminate between each type
219
+ in the union by using ``isinstance `` checks. For example, if you had a variable ``x `` of
220
+ type ``Union[int, str] ``, you could write some code that runs only if ``x `` is an int
221
+ by doing ``if isinstance(x, int): ... ``.
222
+
223
+ However, it is not always possible or convenient to do this. For example, it is not
224
+ possible to use ``isinstance `` to distinguish between two different TypedDicts since
225
+ at runtime, your variable will simply be just a dict.
226
+
227
+ Instead, what you can do is *label * or *tag * your TypedDicts with a distinct Literal
228
+ type. Then, you can discriminate between each kind of TypedDict by checking the label:
229
+
230
+ .. code-block :: python
231
+
232
+ from typing import Literal, TypedDict, Union
233
+
234
+ class NewJobEvent (TypedDict ):
235
+ tag: Literal[" new-job" ]
236
+ job_name: str
237
+ config_file_path: str
238
+
239
+ class CancelJobEvent (TypedDict ):
240
+ tag: Literal[" cancel-job" ]
241
+ job_id: int
242
+
243
+ Event = Union[NewJobEvent, CancelJobEvent]
244
+
245
+ def process_event (event : Event) -> None :
246
+ # Since we made sure both TypedDicts have a key named 'tag', it's
247
+ # safe to do 'event["tag"]'. This expression normally has the type
248
+ # Literal["new-job", "cancel-job"], but the check below will narrow
249
+ # the type to either Literal["new-job"] or Literal["cancel-job"].
250
+ #
251
+ # This in turns narrows the type of 'event' to either NewJobEvent
252
+ # or CancelJobEvent.
253
+ if event[" tag" ] == " new-job" :
254
+ print (event[" job_name" ])
255
+ else :
256
+ print (event[" job_id" ])
257
+
258
+ While this feature is mostly useful when working with TypedDicts, you can also
259
+ use the same technique wih regular objects, tuples, or namedtuples.
260
+
261
+ Similarly, tags do not need to be specifically str Literals: they can be any type
262
+ you can normally narrow within ``if `` statements and the like. For example, you
263
+ could have your tags be int or Enum Literals or even regular classes you narrow
264
+ using ``isinstance() ``:
265
+
266
+ .. code-block :: python
267
+
268
+ from typing import Generic, TypeVar, Union
269
+
270
+ T = TypeVar(' T' )
271
+
272
+ class Wrapper (Generic[T]):
273
+ def __init__ (self , inner : T) -> None :
274
+ self .inner = inner
275
+
276
+ def process (w : Union[Wrapper[int ], Wrapper[str ]]) -> None :
277
+ # Doing `if isinstance(w, Wrapper[int])` does not work: isinstance requires
278
+ # that the second argument always be an *erased* type, with no generics.
279
+ # This is because generics are a typing-only concept and do not exist at
280
+ # runtime in a way `isinstance` can always check.
281
+ #
282
+ # However, we can side-step this by checking the type of `w.inner` to
283
+ # narrow `w` itself:
284
+ if isinstance (w.inner, int ):
285
+ reveal_type(w) # Revealed type is 'Wrapper[int]'
286
+ else :
287
+ reveal_type(w) # Revealed type is 'Wrapper[str]'
288
+
289
+ This feature is sometimes called "sum types" or "discriminated union types"
290
+ in other programming languages.
291
+
171
292
Limitations
172
293
***********
173
294
0 commit comments