Skip to content

bpo-40187: Refactor typing.TypedDict. #19372

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
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 33 additions & 34 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1789,44 +1789,20 @@ def __new__(cls, typename, fields=None, /, **kwargs):
return _make_nmtuple(typename, fields)


def _dict_new(cls, /, *args, **kwargs):
return dict(*args, **kwargs)


def _typeddict_new(cls, typename, fields=None, /, *, total=True, **kwargs):
if fields is None:
fields = kwargs
elif kwargs:
raise TypeError("TypedDict takes either a dict or keyword arguments,"
" but not both")

ns = {'__annotations__': dict(fields), '__total__': total}
try:
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass

return _TypedDictMeta(typename, (), ns)


def _check_fails(cls, other):
# Typed dicts are only for static structural subtyping.
raise TypeError('TypedDict does not support instance and class checks')


class _TypedDictMeta(type):
def __new__(cls, name, bases, ns, total=True):
"""Create new typed dict class object.

This method is called directly when TypedDict is subclassed,
or via _typeddict_new when TypedDict is instantiated. This way
This method is called when TypedDict is subclassed,
or when TypedDict is instantiated. This way
TypedDict supports all three syntax forms described in its docstring.
Subclasses and instances of TypedDict return actual dictionaries
via _dict_new.
Subclasses and instances of TypedDict return actual dictionaries.
"""
ns['__new__'] = _typeddict_new if name == 'TypedDict' else _dict_new
tp_dict = super(_TypedDictMeta, cls).__new__(cls, name, (dict,), ns)
for base in bases:
if type(base) is not _TypedDictMeta:
raise TypeError('cannot inherit from both a TypedDict type '
'and a non-TypedDict base class')
tp_dict = type.__new__(_TypedDictMeta, name, (dict,), ns)

annotations = {}
own_annotations = ns.get('__annotations__', {})
Expand Down Expand Up @@ -1856,10 +1832,16 @@ def __new__(cls, name, bases, ns, total=True):
tp_dict.__total__ = total
return tp_dict

__instancecheck__ = __subclasscheck__ = _check_fails
__call__ = dict # static method

def __subclasscheck__(cls, other):
# Typed dicts are only for static structural subtyping.
raise TypeError('TypedDict does not support instance and class checks')

__instancecheck__ = __subclasscheck__

class TypedDict(dict, metaclass=_TypedDictMeta):

def TypedDict(typename, fields=None, /, *, total=True, **kwargs):
"""A simple typed namespace. At runtime it is equivalent to a plain dict.

TypedDict creates a dictionary type that expects all of its
Expand Down Expand Up @@ -1901,6 +1883,23 @@ class body be required.
The class syntax is only supported in Python 3.6+, while two other
syntax forms work for Python 2.7 and 3.2+
"""
if fields is None:
fields = kwargs
elif kwargs:
raise TypeError("TypedDict takes either a dict or keyword arguments,"
" but not both")

ns = {'__annotations__': dict(fields), '__total__': total}
try:
# Setting correct module is necessary to make typed dict classes pickleable.
ns['__module__'] = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass

return _TypedDictMeta(typename, (), ns)

_TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
TypedDict.__mro_entries__ = lambda bases: (_TypedDict,)


def NewType(name, tp):
Expand Down