@@ -554,58 +554,31 @@ It should be used sparingly.
554
554
555
555
### "The ` Any ` trick"
556
556
557
+ In cases where a function or method can return ` None ` , but where forcing the
558
+ user to explicitly check for ` None ` can be detrimental, use
559
+ ` _typeshed.MaybeNone ` (an alias to ` Any ` ), instead of ` None ` .
560
+
557
561
Consider the following (simplified) signature of ` re.Match[str].group ` :
558
562
559
563
``` python
560
564
class Match :
561
- def group (self , group : str | int , / ) -> str | Any : ...
565
+ def group (self , group : str | int , / ) -> str | MaybeNone : ...
562
566
```
563
567
564
- The ` str | Any ` seems unnecessary and weird at first.
565
- Because ` Any ` includes all strings, you would expect ` str | Any ` to be
566
- equivalent to ` Any ` , but it is not. To understand the difference,
567
- let's look at what happens when type-checking this simplified example:
568
-
569
- Suppose you have a legacy system that for historical reasons has two kinds
570
- of user IDs. Old IDs look like ` "legacy_userid_123" ` and new IDs look like
571
- ` "456_username" ` . The function below is supposed to extract the name
572
- ` "USERNAME" ` from a new ID, and return ` None ` if you give it a legacy ID.
568
+ This avoid forcing the user to check for ` None ` :
573
569
574
570
``` python
575
- import re
576
-
577
- def parse_name_from_new_id (user_id : str ) -> str | None :
578
- match = re.fullmatch(r " \d + _( . * ) " , user_id)
579
- if match is None :
580
- return None
581
- name_group = match.group(1 )
582
- return name_group.uper() # This line is a typo (`uper` --> `upper`)
571
+ match = re.fullmatch(r " \d + _( . * ) " , some_string)
572
+ assert match is not None
573
+ name_group = match.group(1 ) # The user knows that this will never be None
574
+ return name_group.uper() # This typo will be flagged by the type checker
583
575
```
584
576
585
- The ` .group() ` method returns ` None ` when the given group was not a part of the match.
586
- For example, with a regex like ` r"\d+_(.*)|legacy_userid_\d+" ` , we would get a match whose ` .group(1) ` is ` None ` for the user ID ` "legacy_userid_7" ` .
587
- But here the regex is written so that the group always exists, and ` match.group(1) ` cannot return ` None ` .
588
- Match groups are almost always used in this way.
589
-
590
- Let's now consider typeshed's ` -> str | Any ` annotation of the ` .group() ` method:
591
-
592
- * ` -> Any ` would mean "please do not complain" to type checkers.
593
- If ` name_group ` has type ` Any ` , you will get no error for this.
594
- * ` -> str ` would mean "will always be a ` str ` ", which is wrong, and would
595
- cause type checkers to emit errors for code like ` if name_group is None ` .
596
- * ` -> str | None ` means "you must check for None", which is correct but can get
597
- annoying for some common patterns. Checks like ` assert name_group is not None `
598
- would need to be added into various places only to satisfy type checkers,
599
- even when it is impossible to actually get a ` None ` value
600
- (type checkers aren't smart enough to know this).
601
- * ` -> str | Any ` means "must be prepared to handle a ` str ` ". You will get an
602
- error for ` name_group.uper ` , because it is not valid when ` name_group ` is a
603
- ` str ` . But type checkers are happy with ` if name_group is None ` checks,
604
- because we're saying it can also be something else than an ` str ` .
605
-
606
- In typeshed we unofficially call returning ` Foo | Any ` "the Any trick".
607
- We tend to use it whenever something can be ` None ` ,
608
- but requiring users to check for ` None ` would be more painful than helpful.
577
+ In this case, the user of ` match.group() ` must be prepared to handle a ` str ` ,
578
+ but type checkers are happy with ` if name_group is None ` checks, because we're
579
+ saying it can also be something else than an ` str ` .
580
+
581
+ This is sometimes called "the Any trick".
609
582
610
583
## Submitting Changes
611
584
0 commit comments