-
-
Couldn't load subscription status.
- Fork 33.2k
bpo-43923: Allow NamedTuple multiple inheritance #31779
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
Changes from 4 commits
deaffcd
a93e0d2
a921abd
ec69c42
b3d8c38
4daa7f2
2d66516
031af03
bf5e7a1
0c94643
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 |
|---|---|---|
|
|
@@ -2595,13 +2595,15 @@ def __round__(self, ndigits: int = 0) -> T_co: | |
| pass | ||
|
|
||
|
|
||
| def _make_nmtuple(name, types, module, defaults = ()): | ||
| fields = [n for n, t in types] | ||
| types = {n: _type_check(t, f"field {n} annotation must be a type") | ||
| for n, t in types} | ||
| nm_tpl = collections.namedtuple(name, fields, | ||
| defaults=defaults, module=module) | ||
| nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = types | ||
| def _make_nmtuple(name, types): | ||
| msg = "NamedTuple('Name', [(f0, t0), (f1, t1), ...]); each t must be a type" | ||
| types = [(n, _type_check(t, msg)) for n, t in types] | ||
| nm_tpl = collections.namedtuple(name, [n for n, t in types]) | ||
|
||
| nm_tpl.__annotations__ = dict(types) | ||
| try: | ||
| nm_tpl.__module__ = sys._getframe(2).f_globals.get('__name__', '__main__') | ||
| except (AttributeError, ValueError): | ||
| pass | ||
| return nm_tpl | ||
|
|
||
|
|
||
|
|
@@ -2616,20 +2618,26 @@ def _make_nmtuple(name, types, module, defaults = ()): | |
| class NamedTupleMeta(type): | ||
|
|
||
| def __new__(cls, typename, bases, ns): | ||
| assert bases[0] is _NamedTuple | ||
| if ns.get('_root', False): | ||
| return super().__new__(cls, typename, bases, ns) | ||
| assert bases[0] is NamedTuple | ||
| types = ns.get('__annotations__', {}) | ||
| default_names = [] | ||
| nm_tpl = _make_nmtuple(typename, types.items()) | ||
| defaults = [] | ||
| defaults_dict = {} | ||
| for field_name in types: | ||
| if field_name in ns: | ||
| default_names.append(field_name) | ||
| elif default_names: | ||
| raise TypeError(f"Non-default namedtuple field {field_name} " | ||
| f"cannot follow default field" | ||
| f"{'s' if len(default_names) > 1 else ''} " | ||
| f"{', '.join(default_names)}") | ||
| nm_tpl = _make_nmtuple(typename, types.items(), | ||
| defaults=[ns[n] for n in default_names], | ||
| module=ns['__module__']) | ||
| default_value = ns[field_name] | ||
| defaults.append(default_value) | ||
| defaults_dict[field_name] = default_value | ||
| elif defaults: | ||
| raise TypeError("Non-default namedtuple field {field_name} cannot " | ||
| "follow default field(s) {default_names}" | ||
| .format(field_name=field_name, | ||
| default_names=', '.join(defaults_dict.keys()))) | ||
| nm_tpl.__new__.__annotations__ = dict(types) | ||
| nm_tpl.__new__.__defaults__ = tuple(defaults) | ||
| nm_tpl._field_defaults = defaults_dict | ||
|
||
| # update from user namespace without overriding special namedtuple attributes | ||
| for key in ns: | ||
| if key in _prohibited: | ||
|
|
@@ -2639,7 +2647,7 @@ def __new__(cls, typename, bases, ns): | |
| return nm_tpl | ||
|
|
||
|
|
||
| def NamedTuple(typename, fields=None, /, **kwargs): | ||
| class NamedTuple(metaclass=NamedTupleMeta): | ||
| """Typed version of namedtuple. | ||
|
|
||
| Usage in Python versions >= 3.6:: | ||
|
|
@@ -2663,22 +2671,15 @@ class Employee(NamedTuple): | |
|
|
||
| Employee = NamedTuple('Employee', [('name', str), ('id', int)]) | ||
| """ | ||
| if fields is None: | ||
| fields = kwargs.items() | ||
| elif kwargs: | ||
| raise TypeError("Either list of fields or keywords" | ||
| " can be provided to NamedTuple, not both") | ||
| return _make_nmtuple(typename, fields, module=_caller()) | ||
|
|
||
| _NamedTuple = type.__new__(NamedTupleMeta, 'NamedTuple', (), {}) | ||
|
|
||
| def _namedtuple_mro_entries(bases): | ||
| if len(bases) > 1: | ||
| raise TypeError("Multiple inheritance with NamedTuple is not supported") | ||
| assert bases[0] is NamedTuple | ||
| return (_NamedTuple,) | ||
|
|
||
| NamedTuple.__mro_entries__ = _namedtuple_mro_entries | ||
| _root = True | ||
|
|
||
| def __new__(cls, typename, fields=None, /, **kwargs): | ||
| if fields is None: | ||
| fields = kwargs.items() | ||
| elif kwargs: | ||
| raise TypeError("Either list of fields or keywords" | ||
| " can be provided to NamedTuple, not both") | ||
| return _make_nmtuple(typename, fields) | ||
|
|
||
|
|
||
| class _TypedDictMeta(type): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Multiple inheritance with :class:`typing.NamedTuple` now no longer raises an | ||
| error. |
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.
You're missing the improved error message from the existing code