Skip to content

bpo-41810: Reintroduce types.EllipsisType, .NoneType & .NotImplementedType #22336

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

Merged
merged 13 commits into from
Sep 22, 2020
Merged
8 changes: 5 additions & 3 deletions Doc/library/constants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ A small number of constants live in the built-in namespace. They are:

.. data:: None

The sole value of the type ``NoneType``. ``None`` is frequently used to
The sole value of the type :data:`types.NoneType`. ``None`` is frequently used to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"The sole value of the type NoneType" is most easily read (parsed) as referring to NoneType's usefulness. I expected it to be followed by 'is to ...' and initially read 'is frequently' as a grammatical error. It took awhile to realize that this is a convoluted way to say 'None'. I strongly recommend a rewording.

Suggested change
The sole value of the type :data:`types.NoneType`. ``None`` is frequently used to
''None'' is the only instance of :data:`types.NoneType`. It is frequently used to

types.NoneType is already twice identified as a type, so a third mention is not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on your and Guido's feedback (#22336 (review)) I'm proposing something like this:

.. data:: None

   An object frequently used to represent the absence of a value, as when
   default arguments are not passed to a function. Assignments to ``None``
   are illegal and raise a :exc:`SyntaxError`.
   Sole instance of the :data:`types.NoneType` type.

Any thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's much better, I think. Although I'd make the last sentence (with the correct markup): "None is the sole instance of the NoneType type."

represent the absence of a value, as when default arguments are not passed to a
function. Assignments to ``None`` are illegal and raise a :exc:`SyntaxError`.


.. data:: NotImplemented

Special value which should be returned by the binary special methods
Sole value of the type :data:`types.NotImplementedType` and a
special value which should be returned by the binary special methods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above. Use a parallel construction.

Suggested change
Sole value of the type :data:`types.NotImplementedType` and a
special value which should be returned by the binary special methods
``NotImplemented is the only instance of :data:`types.NotImplementedType`. It is a
special value which should be returned by the binary special methods

(e.g. :meth:`__eq__`, :meth:`__lt__`, :meth:`__add__`, :meth:`__rsub__`,
etc.) to indicate that the operation is not implemented with respect to
the other type; may be returned by the in-place binary special methods
Expand Down Expand Up @@ -59,7 +60,8 @@ A small number of constants live in the built-in namespace. They are:
.. index:: single: ...; ellipsis literal
.. data:: Ellipsis

The same as the ellipsis literal "``...``". Special value used mostly in conjunction
The same as the ellipsis literal "``...``" and sole value of the type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The same as the ellipsis literal "``...``" and sole value of the type
The same as the ellipsis literal "``...``" and the only instance of

:data:`types.EllipsisType`. Special value used mostly in conjunction
with extended slicing syntax for user-defined container data types.


Expand Down
21 changes: 21 additions & 0 deletions Doc/library/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ If you instantiate any of these types, note that signatures may vary between Pyt

Standard names are defined for the following types:

.. data:: NoneType

The type of :data:`None`.

.. versionadded:: 3.10


.. data:: FunctionType
LambdaType

Expand Down Expand Up @@ -186,6 +193,13 @@ Standard names are defined for the following types:
.. versionadded:: 3.7


.. data:: NotImplementedType

The type of :data:`NotImplemented`.

.. versionadded:: 3.10


.. data:: MethodDescriptorType

The type of methods of some built-in data types such as :meth:`str.join`.
Expand Down Expand Up @@ -236,6 +250,13 @@ Standard names are defined for the following types:
Defaults to ``None``. Previously the attribute was optional.


.. data:: EllipsisType

The type of :data:`Ellipsis`.

.. versionadded:: 3.10


.. class:: TracebackType(tb_next, tb_frame, tb_lasti, tb_lineno)

The type of traceback objects such as found in ``sys.exc_info()[2]``.
Expand Down
7 changes: 7 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ Add :data:`sys.orig_argv` attribute: the list of the original command line
arguments passed to the Python executable.
(Contributed by Victor Stinner in :issue:`23427`.)

types
-----

Reintroduced the :data:`types.EllipsisType`, :data:`types.NoneType`
and :data:`types.NotImplementedType` classes.
(Contributed by Bas van Beek in :issue:`41810`.)

unittest
--------

Expand Down
9 changes: 5 additions & 4 deletions Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
:copyright: Copyright 2008 by Armin Ronacher.
:license: Python License.
"""
import types
import sys
from _ast import *
from contextlib import contextmanager, nullcontext
Expand Down Expand Up @@ -571,22 +572,22 @@ def __new__(cls, *args, **kwargs):
Num: (int, float, complex),
Str: (str,),
Bytes: (bytes,),
NameConstant: (type(None), bool),
Ellipsis: (type(...),),
NameConstant: (types.NoneType, bool),
Ellipsis: (types.EllipsisType,),
}
_const_types_not = {
Num: (bool,),
}

_const_node_type_names = {
bool: 'NameConstant', # should be before int
type(None): 'NameConstant',
types.NoneType: 'NameConstant',
int: 'Num',
float: 'Num',
complex: 'Num',
str: 'Str',
bytes: 'Bytes',
type(...): 'Ellipsis',
types.EllipsisType: 'Ellipsis',
}

class slice(AST):
Expand Down
12 changes: 6 additions & 6 deletions Lib/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ def copy(x):

def _copy_immutable(x):
return x
for t in (type(None), int, float, bool, complex, str, tuple,
for t in (types.NoneType, int, float, bool, complex, str, tuple,
bytes, frozenset, type, range, slice, property,
types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
types.FunctionType, weakref.ref):
types.BuiltinFunctionType, types.EllipsisType,
types.NotImplementedType, types.FunctionType, weakref.ref):
d[t] = _copy_immutable
t = getattr(types, "CodeType", None)
if t is not None:
Expand Down Expand Up @@ -181,9 +181,9 @@ def deepcopy(x, memo=None, _nil=[]):

def _deepcopy_atomic(x, memo):
return x
d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic
d[type(NotImplemented)] = _deepcopy_atomic
d[types.NoneType] = _deepcopy_atomic
d[types.EllipsisType] = _deepcopy_atomic
d[types.NotImplementedType] = _deepcopy_atomic
d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic
Expand Down
4 changes: 2 additions & 2 deletions Lib/distutils/unixccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* link shared library handled by 'cc -shared'
"""

import os, sys, re
import os, sys, re, types

from distutils import sysconfig
from distutils.dep_util import newer
Expand Down Expand Up @@ -157,7 +157,7 @@ def link(self, target_desc, objects,

lib_opts = gen_lib_options(self, library_dirs, runtime_library_dirs,
libraries)
if not isinstance(output_dir, (str, type(None))):
if not isinstance(output_dir, (str, types.NoneType)):
raise TypeError("'output_dir' must be a string or None")
if output_dir is not None:
output_filename = os.path.join(output_dir, output_filename)
Expand Down
3 changes: 2 additions & 1 deletion Lib/idlelib/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import _thread as thread
import threading
import warnings
import types

from idlelib import autocomplete # AutoComplete, fetch_encodings
from idlelib import calltip # Calltip
Expand Down Expand Up @@ -558,7 +559,7 @@ def runcode(self, code):
except SystemExit as e:
if e.args: # SystemExit called with an argument.
ob = e.args[0]
if not isinstance(ob, (type(None), int)):
if not isinstance(ob, (types.NoneType, int)):
print('SystemExit: ' + str(ob), file=sys.stderr)
# Return to the interactive prompt.
except:
Expand Down
2 changes: 1 addition & 1 deletion Lib/imp.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def find_module(name, path=None):
"""
if not isinstance(name, str):
raise TypeError("'name' must be a str, not {}".format(type(name)))
elif not isinstance(path, (type(None), list)):
elif not isinstance(path, (types.NoneType, list)):
# Backwards-compatibility
raise RuntimeError("'path' must be None or a list, "
"not {}".format(type(path)))
Expand Down
2 changes: 1 addition & 1 deletion Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2034,7 +2034,7 @@ def wrap_value(s):
except NameError:
raise RuntimeError()

if isinstance(value, (str, int, float, bytes, bool, type(None))):
if isinstance(value, (str, int, float, bytes, bool, types.NoneType)):
return ast.Constant(value)
raise RuntimeError()

Expand Down
3 changes: 0 additions & 3 deletions Lib/lib2to3/fixes/fix_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@
'ComplexType' : 'complex',
'DictType': 'dict',
'DictionaryType' : 'dict',
'EllipsisType' : 'type(Ellipsis)',
#'FileType' : 'io.IOBase',
'FloatType': 'float',
'IntType': 'int',
'ListType': 'list',
'LongType': 'int',
'ObjectType' : 'object',
'NoneType': 'type(None)',
'NotImplementedType' : 'type(NotImplemented)',
'SliceType' : 'slice',
'StringType': 'bytes', # XXX ?
'StringTypes' : '(str,)', # XXX ?
Expand Down
4 changes: 0 additions & 4 deletions Lib/lib2to3/tests/test_fixers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3276,10 +3276,6 @@ def test_basic_types_convert(self):
a = """int"""
self.check(b, a)

b = """types.NoneType"""
a = """type(None)"""
self.check(b, a)

b = "types.StringTypes"
a = "(str,)"
self.check(b, a)
Expand Down
12 changes: 6 additions & 6 deletions Lib/pickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

"""

from types import FunctionType
import types
from copyreg import dispatch_table
from copyreg import _extension_registry, _inverted_registry, _extension_cache
from itertools import islice
Expand Down Expand Up @@ -737,7 +737,7 @@ def save_reduce(self, func, args, state=None, listitems=None,

def save_none(self, obj):
self.write(NONE)
dispatch[type(None)] = save_none
dispatch[types.NoneType] = save_none

def save_bool(self, obj):
if self.proto >= 2:
Expand Down Expand Up @@ -1117,15 +1117,15 @@ def save_global(self, obj, name=None):
self.memoize(obj)

def save_type(self, obj):
if obj is type(None):
if obj is types.NoneType:
return self.save_reduce(type, (None,), obj=obj)
elif obj is type(NotImplemented):
elif obj is types.NotImplementedType:
return self.save_reduce(type, (NotImplemented,), obj=obj)
elif obj is type(...):
elif obj is types.EllipsisType:
return self.save_reduce(type, (...,), obj=obj)
return self.save_global(obj)

dispatch[FunctionType] = save_global
dispatch[types.FunctionType] = save_global
dispatch[type] = save_type


Expand Down
3 changes: 2 additions & 1 deletion Lib/pickletools.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import pickle
import re
import sys
import types

__all__ = ['dis', 'genops', 'optimize']

Expand Down Expand Up @@ -1017,7 +1018,7 @@ def __repr__(self):

pynone = StackObject(
name="None",
obtype=type(None),
obtype=types.NoneType,
doc="The Python None object.")

pytuple = StackObject(
Expand Down
2 changes: 1 addition & 1 deletion Lib/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ def _safe_repr(object, context, maxlevels, level, sort_dicts):
return rep, (rep and not rep.startswith('<')), False

_builtin_scalars = frozenset({str, bytes, bytearray, int, float, complex,
bool, type(None)})
bool, _types.NoneType})

def _recursion(object):
return ("<Recursion on %s with id=%s>"
Expand Down
2 changes: 1 addition & 1 deletion Lib/pydoc_data/topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,7 @@
'explicitly return a\n'
'value. It supports no special operations. There is '
'exactly one null\n'
'object, named "None" (a built-in name). "type(None)()" '
'object, named "None" (a built-in name). "types.NoneType()" '
'produces the\n'
'same singleton.\n'
'\n'
Expand Down
3 changes: 2 additions & 1 deletion Lib/random.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from bisect import bisect as _bisect
import os as _os
import _random
import types

try:
# hashlib is pretty heavy to load, try lean internal module first
Expand Down Expand Up @@ -154,7 +155,7 @@ def seed(self, a=None, version=2):
a += _sha512(a).digest()
a = int.from_bytes(a, 'big')

elif not isinstance(a, (type(None), int, float, str, bytes, bytearray)):
elif not isinstance(a, (types.NoneType, int, float, str, bytes, bytearray)):
_warn('Seeding based on hashing is deprecated\n'
'since Python 3.9 and will be removed in a subsequent '
'version. The only \n'
Expand Down
2 changes: 1 addition & 1 deletion Lib/runpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def run_path(path_name, init_globals=None, run_name=None):
if type(importer).__module__ == 'imp':
if type(importer).__name__ == 'NullImporter':
is_NullImporter = True
if isinstance(importer, type(None)) or is_NullImporter:
if isinstance(importer, types.NoneType) or is_NullImporter:
# Not a valid sys.path entry, so run the code directly
# execfile() doesn't help as we want to allow compiled files
code, fname = _get_code_from_file(run_name, path_name)
Expand Down
9 changes: 5 additions & 4 deletions Lib/sqlite3/test/userfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.

import types
import unittest
import unittest.mock
import sqlite3 as sqlite
Expand Down Expand Up @@ -49,7 +50,7 @@ def func_isint(v):
def func_isfloat(v):
return type(v) is float
def func_isnone(v):
return type(v) is type(None)
return type(v) is types.NoneType
def func_isblob(v):
return isinstance(v, (bytes, memoryview))
def func_islonglong(v):
Expand Down Expand Up @@ -107,7 +108,7 @@ def __init__(self):
self.val = None

def step(self, whichType, val):
theType = {"str": str, "int": int, "float": float, "None": type(None),
theType = {"str": str, "int": int, "float": float, "None": types.NoneType,
"blob": bytes}
self.val = int(theType[whichType] is type(val))

Expand All @@ -119,7 +120,7 @@ def __init__(self):
self.val = 0

def step(self, whichType, *vals):
theType = {"str": str, "int": int, "float": float, "None": type(None),
theType = {"str": str, "int": int, "float": float, "None": types.NoneType,
"blob": bytes}
for val in vals:
self.val += int(theType[whichType] is type(val))
Expand Down Expand Up @@ -211,7 +212,7 @@ def CheckFuncReturnNull(self):
cur = self.con.cursor()
cur.execute("select returnnull()")
val = cur.fetchone()[0]
self.assertEqual(type(val), type(None))
self.assertEqual(type(val), types.NoneType)
self.assertEqual(val, None)

def CheckFuncReturnBlob(self):
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/test_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import inspect
import builtins
import unittest
import types
from unittest.mock import Mock
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional
from typing import get_type_hints
Expand Down Expand Up @@ -2026,7 +2027,7 @@ class C:
def test_docstring_one_field_with_default_none(self):
@dataclass
class C:
x: Union[int, type(None)] = None
x: Union[int, types.NoneType] = None

self.assertDocStrEqual(C.__doc__, "C(x:Optional[int]=None)")

Expand Down Expand Up @@ -2955,7 +2956,7 @@ def test_text_annotations(self):
self.assertEqual(
get_type_hints(dataclass_textanno.Bar.__init__),
{'foo': dataclass_textanno.Foo,
'return': type(None)})
'return': types.NoneType})


class TestMakeDataclass(unittest.TestCase):
Expand Down
Loading