-
-
Notifications
You must be signed in to change notification settings - Fork 110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Backport NewType
as it exists on py310+
#157
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -440,7 +440,6 @@ def clear_overloads(): | |
Counter = typing.Counter | ||
ChainMap = typing.ChainMap | ||
AsyncGenerator = typing.AsyncGenerator | ||
NewType = typing.NewType | ||
Text = typing.Text | ||
TYPE_CHECKING = typing.TYPE_CHECKING | ||
|
||
|
@@ -2546,3 +2545,68 @@ class Baz(list[str]): ... | |
raise TypeError( | ||
f'Expected an instance of type, not {type(__cls).__name__!r}' | ||
) from None | ||
|
||
|
||
# NewType is a class on Python 3.10+, making it pickleable | ||
# The error message for subclassing instances of NewType was improved on 3.11+ | ||
if sys.version_info >= (3, 11): | ||
NewType = typing.NewType | ||
else: | ||
class NewType: | ||
"""NewType creates simple unique types with almost zero | ||
runtime overhead. NewType(name, tp) is considered a subtype of tp | ||
by static type checkers. At runtime, NewType(name, tp) returns | ||
a dummy callable that simply returns its argument. Usage:: | ||
UserId = NewType('UserId', int) | ||
def name_by_id(user_id: UserId) -> str: | ||
... | ||
UserId('user') # Fails type check | ||
name_by_id(42) # Fails type check | ||
name_by_id(UserId(42)) # OK | ||
num = UserId(5) + 1 # type: int | ||
""" | ||
|
||
def __call__(self, obj): | ||
return obj | ||
|
||
def __init__(self, name, tp): | ||
self.__qualname__ = name | ||
if '.' in name: | ||
name = name.rpartition('.')[-1] | ||
self.__name__ = name | ||
self.__supertype__ = tp | ||
def_mod = _caller() | ||
if def_mod != 'typing_extensions': | ||
self.__module__ = def_mod | ||
|
||
def __mro_entries__(self, bases): | ||
# We defined __mro_entries__ to get a better error message | ||
# if a user attempts to subclass a NewType instance. bpo-46170 | ||
supercls_name = self.__name__ | ||
|
||
class Dummy: | ||
def __init_subclass__(cls): | ||
subcls_name = cls.__name__ | ||
raise TypeError( | ||
f"Cannot subclass an instance of NewType. " | ||
f"Perhaps you were looking for: " | ||
f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`" | ||
) | ||
|
||
return (Dummy,) | ||
|
||
def __repr__(self): | ||
return f'{self.__module__}.{self.__qualname__}' | ||
|
||
def __reduce__(self): | ||
return self.__qualname__ | ||
|
||
if sys.version_info >= (3, 10): | ||
# PEP 604 methods | ||
# It doesn't make sense to have these methods on Python <3.10 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could allow this in theory, but it would probably be more confusing than useful to users to allow There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the other hand:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hahaha that seems wrong though :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess there's no harm in implementing the methods on Python <3.10, but it feels really weird doing an ad-hoc backport of PEP 604 based on the classes we happen to be reimplementing in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like we don't backport PEP 604 for Python 3.7.16 (default, Jan 17 2023, 16:06:28) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing_extensions
>>> typing_extensions.TypeVar("T") | int
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'TypeVar' and 'type'
>>> typing_extensions.ParamSpec("P") | int
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'ParamSpec' and 'type' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, let's leave this as you wrote it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed that it's not worth it to remove |
||
|
||
def __or__(self, other): | ||
return typing.Union[self, other] | ||
|
||
def __ror__(self, other): | ||
return typing.Union[other, self] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this would be clearer as
as it may not be obvious to users that "an instance of NewType" refers to a type. I suppose this is the same error as CPython though, so up to you whether it's worth changing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nice thing about the "an instance of NewType" wording is that it explains why you can't subclass the thing you're trying to subclass (it's an instance, not a class). But maybe the current phrasing is too terse for the explanation to be useful to people who aren't familiar with how NewType works.