diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -33,185 +33,190 @@ They also help you determine when you can expect to find the following special attributes: -+-----------+-----------------+---------------------------+ -| Type | Attribute | Description | -+===========+=================+===========================+ -| module | __doc__ | documentation string | -+-----------+-----------------+---------------------------+ -| | __file__ | filename (missing for | -| | | built-in modules) | -+-----------+-----------------+---------------------------+ -| class | __doc__ | documentation string | -+-----------+-----------------+---------------------------+ -| | __name__ | name with which this | -| | | class was defined | -+-----------+-----------------+---------------------------+ -| | __qualname__ | qualified name | -+-----------+-----------------+---------------------------+ -| | __module__ | name of module in which | -| | | this class was defined | -+-----------+-----------------+---------------------------+ -| method | __doc__ | documentation string | -+-----------+-----------------+---------------------------+ -| | __name__ | name with which this | -| | | method was defined | -+-----------+-----------------+---------------------------+ -| | __qualname__ | qualified name | -+-----------+-----------------+---------------------------+ -| | __func__ | function object | -| | | containing implementation | -| | | of method | -+-----------+-----------------+---------------------------+ -| | __self__ | instance to which this | -| | | method is bound, or | -| | | ``None`` | -+-----------+-----------------+---------------------------+ -| function | __doc__ | documentation string | -+-----------+-----------------+---------------------------+ -| | __name__ | name with which this | -| | | function was defined | -+-----------+-----------------+---------------------------+ -| | __qualname__ | qualified name | -+-----------+-----------------+---------------------------+ -| | __code__ | code object containing | -| | | compiled function | -| | | :term:`bytecode` | -+-----------+-----------------+---------------------------+ -| | __defaults__ | tuple of any default | -| | | values for positional or | -| | | keyword parameters | -+-----------+-----------------+---------------------------+ -| | __kwdefaults__ | mapping of any default | -| | | values for keyword-only | -| | | parameters | -+-----------+-----------------+---------------------------+ -| | __globals__ | global namespace in which | -| | | this function was defined | -+-----------+-----------------+---------------------------+ -| | __annotations__ | mapping of parameters | -| | | names to annotations; | -| | | ``"return"`` key is | -| | | reserved for return | -| | | annotations. | -+-----------+-----------------+---------------------------+ -| traceback | tb_frame | frame object at this | -| | | level | -+-----------+-----------------+---------------------------+ -| | tb_lasti | index of last attempted | -| | | instruction in bytecode | -+-----------+-----------------+---------------------------+ -| | tb_lineno | current line number in | -| | | Python source code | -+-----------+-----------------+---------------------------+ -| | tb_next | next inner traceback | -| | | object (called by this | -| | | level) | -+-----------+-----------------+---------------------------+ -| frame | f_back | next outer frame object | -| | | (this frame's caller) | -+-----------+-----------------+---------------------------+ -| | f_builtins | builtins namespace seen | -| | | by this frame | -+-----------+-----------------+---------------------------+ -| | f_code | code object being | -| | | executed in this frame | -+-----------+-----------------+---------------------------+ -| | f_globals | global namespace seen by | -| | | this frame | -+-----------+-----------------+---------------------------+ -| | f_lasti | index of last attempted | -| | | instruction in bytecode | -+-----------+-----------------+---------------------------+ -| | f_lineno | current line number in | -| | | Python source code | -+-----------+-----------------+---------------------------+ -| | f_locals | local namespace seen by | -| | | this frame | -+-----------+-----------------+---------------------------+ -| | f_restricted | 0 or 1 if frame is in | -| | | restricted execution mode | -+-----------+-----------------+---------------------------+ -| | f_trace | tracing function for this | -| | | frame, or ``None`` | -+-----------+-----------------+---------------------------+ -| code | co_argcount | number of arguments (not | -| | | including \* or \*\* | -| | | args) | -+-----------+-----------------+---------------------------+ -| | co_code | string of raw compiled | -| | | bytecode | -+-----------+-----------------+---------------------------+ -| | co_consts | tuple of constants used | -| | | in the bytecode | -+-----------+-----------------+---------------------------+ -| | co_filename | name of file in which | -| | | this code object was | -| | | created | -+-----------+-----------------+---------------------------+ -| | co_firstlineno | number of first line in | -| | | Python source code | -+-----------+-----------------+---------------------------+ -| | co_flags | bitmap: 1=optimized ``|`` | -| | | 2=newlocals ``|`` 4=\*arg | -| | | ``|`` 8=\*\*arg | -+-----------+-----------------+---------------------------+ -| | co_lnotab | encoded mapping of line | -| | | numbers to bytecode | -| | | indices | -+-----------+-----------------+---------------------------+ -| | co_name | name with which this code | -| | | object was defined | -+-----------+-----------------+---------------------------+ -| | co_names | tuple of names of local | -| | | variables | -+-----------+-----------------+---------------------------+ -| | co_nlocals | number of local variables | -+-----------+-----------------+---------------------------+ -| | co_stacksize | virtual machine stack | -| | | space required | -+-----------+-----------------+---------------------------+ -| | co_varnames | tuple of names of | -| | | arguments and local | -| | | variables | -+-----------+-----------------+---------------------------+ -| generator | __name__ | name | -+-----------+-----------------+---------------------------+ -| | __qualname__ | qualified name | -+-----------+-----------------+---------------------------+ -| | gi_frame | frame | -+-----------+-----------------+---------------------------+ -| | gi_running | is the generator running? | -+-----------+-----------------+---------------------------+ -| | gi_code | code | -+-----------+-----------------+---------------------------+ -| | gi_yieldfrom | object being iterated by | -| | | ``yield from``, or | -| | | ``None`` | -+-----------+-----------------+---------------------------+ -| coroutine | __name__ | name | -+-----------+-----------------+---------------------------+ -| | __qualname__ | qualified name | -+-----------+-----------------+---------------------------+ -| | cr_await | object being awaited on, | -| | | or ``None`` | -+-----------+-----------------+---------------------------+ -| | cr_frame | frame | -+-----------+-----------------+---------------------------+ -| | cr_running | is the coroutine running? | -+-----------+-----------------+---------------------------+ -| | cr_code | code | -+-----------+-----------------+---------------------------+ -| builtin | __doc__ | documentation string | -+-----------+-----------------+---------------------------+ -| | __name__ | original name of this | -| | | function or method | -+-----------+-----------------+---------------------------+ -| | __qualname__ | qualified name | -+-----------+-----------------+---------------------------+ -| | __self__ | instance to which a | -| | | method is bound, or | -| | | ``None`` | -+-----------+-----------------+---------------------------+ ++-----------+----------------------+---------------------------+ +| Type | Attribute | Description | ++===========+======================+===========================+ +| module | __doc__ | documentation string | ++-----------+----------------------+---------------------------+ +| | __file__ | filename (missing for | +| | | built-in modules) | ++-----------+----------------------+---------------------------+ +| class | __doc__ | documentation string | ++-----------+----------------------+---------------------------+ +| | __definition_order__ | the names of the class's | +| | | attrs, in the order in | +| | | which they were defined | +| | | (special names excluded) | ++-----------+----------------------+---------------------------+ +| | __name__ | name with which this | +| | | class was defined | ++-----------+----------------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+----------------------+---------------------------+ +| | __module__ | name of module in which | +| | | this class was defined | ++-----------+----------------------+---------------------------+ +| method | __doc__ | documentation string | ++-----------+----------------------+---------------------------+ +| | __name__ | name with which this | +| | | method was defined | ++-----------+----------------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+----------------------+---------------------------+ +| | __func__ | function object | +| | | containing implementation | +| | | of method | ++-----------+----------------------+---------------------------+ +| | __self__ | instance to which this | +| | | method is bound, or | +| | | ``None`` | ++-----------+----------------------+---------------------------+ +| function | __doc__ | documentation string | ++-----------+----------------------+---------------------------+ +| | __name__ | name with which this | +| | | function was defined | ++-----------+----------------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+----------------------+---------------------------+ +| | __code__ | code object containing | +| | | compiled function | +| | | :term:`bytecode` | ++-----------+----------------------+---------------------------+ +| | __defaults__ | tuple of any default | +| | | values for positional or | +| | | keyword parameters | ++-----------+----------------------+---------------------------+ +| | __kwdefaults__ | mapping of any default | +| | | values for keyword-only | +| | | parameters | ++-----------+----------------------+---------------------------+ +| | __globals__ | global namespace in which | +| | | this function was defined | ++-----------+----------------------+---------------------------+ +| | __annotations__ | mapping of parameters | +| | | names to annotations; | +| | | ``"return"`` key is | +| | | reserved for return | +| | | annotations. | ++-----------+----------------------+---------------------------+ +| traceback | tb_frame | frame object at this | +| | | level | ++-----------+----------------------+---------------------------+ +| | tb_lasti | index of last attempted | +| | | instruction in bytecode | ++-----------+----------------------+---------------------------+ +| | tb_lineno | current line number in | +| | | Python source code | ++-----------+----------------------+---------------------------+ +| | tb_next | next inner traceback | +| | | object (called by this | +| | | level) | ++-----------+----------------------+---------------------------+ +| frame | f_back | next outer frame object | +| | | (this frame's caller) | ++-----------+----------------------+---------------------------+ +| | f_builtins | builtins namespace seen | +| | | by this frame | ++-----------+----------------------+---------------------------+ +| | f_code | code object being | +| | | executed in this frame | ++-----------+----------------------+---------------------------+ +| | f_globals | global namespace seen by | +| | | this frame | ++-----------+----------------------+---------------------------+ +| | f_lasti | index of last attempted | +| | | instruction in bytecode | ++-----------+----------------------+---------------------------+ +| | f_lineno | current line number in | +| | | Python source code | ++-----------+----------------------+---------------------------+ +| | f_locals | local namespace seen by | +| | | this frame | ++-----------+----------------------+---------------------------+ +| | f_restricted | 0 or 1 if frame is in | +| | | restricted execution mode | ++-----------+----------------------+---------------------------+ +| | f_trace | tracing function for this | +| | | frame, or ``None`` | ++-----------+----------------------+---------------------------+ +| code | co_argcount | number of arguments (not | +| | | including \* or \*\* | +| | | args) | ++-----------+----------------------+---------------------------+ +| | co_code | string of raw compiled | +| | | bytecode | ++-----------+----------------------+---------------------------+ +| | co_consts | tuple of constants used | +| | | in the bytecode | ++-----------+----------------------+---------------------------+ +| | co_filename | name of file in which | +| | | this code object was | +| | | created | ++-----------+----------------------+---------------------------+ +| | co_firstlineno | number of first line in | +| | | Python source code | ++-----------+----------------------+---------------------------+ +| | co_flags | bitmap: 1=optimized ``|`` | +| | | 2=newlocals ``|`` 4=\*arg | +| | | ``|`` 8=\*\*arg | ++-----------+----------------------+---------------------------+ +| | co_lnotab | encoded mapping of line | +| | | numbers to bytecode | +| | | indices | ++-----------+----------------------+---------------------------+ +| | co_name | name with which this code | +| | | object was defined | ++-----------+----------------------+---------------------------+ +| | co_names | tuple of names of local | +| | | variables | ++-----------+----------------------+---------------------------+ +| | co_nlocals | number of local variables | ++-----------+----------------------+---------------------------+ +| | co_stacksize | virtual machine stack | +| | | space required | ++-----------+----------------------+---------------------------+ +| | co_varnames | tuple of names of | +| | | arguments and local | +| | | variables | ++-----------+----------------------+---------------------------+ +| generator | __name__ | name | ++-----------+----------------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+----------------------+---------------------------+ +| | gi_frame | frame | ++-----------+----------------------+---------------------------+ +| | gi_running | is the generator running? | ++-----------+----------------------+---------------------------+ +| | gi_code | code | ++-----------+----------------------+---------------------------+ +| | gi_yieldfrom | object being iterated by | +| | | ``yield from``, or | +| | | ``None`` | ++-----------+----------------------+---------------------------+ +| coroutine | __name__ | name | ++-----------+----------------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+----------------------+---------------------------+ +| | cr_await | object being awaited on, | +| | | or ``None`` | ++-----------+----------------------+---------------------------+ +| | cr_frame | frame | ++-----------+----------------------+---------------------------+ +| | cr_running | is the coroutine running? | ++-----------+----------------------+---------------------------+ +| | cr_code | code | ++-----------+----------------------+---------------------------+ +| builtin | __doc__ | documentation string | ++-----------+----------------------+---------------------------+ +| | __name__ | original name of this | +| | | function or method | ++-----------+----------------------+---------------------------+ +| | __qualname__ | qualified name | ++-----------+----------------------+---------------------------+ +| | __self__ | instance to which a | +| | | method is bound, or | +| | | ``None`` | ++-----------+----------------------+---------------------------+ .. versionchanged:: 3.5 @@ -220,6 +225,10 @@ The ``__name__`` attribute of generators is now set from the function name, instead of the code name, and it can now be modified. +.. versionchanged:: 3.6 + + Add ``__definition_order__`` to classes. + .. function:: getmembers(object[, predicate]) diff --git a/Doc/library/types.rst b/Doc/library/types.rst --- a/Doc/library/types.rst +++ b/Doc/library/types.rst @@ -55,6 +55,11 @@ .. versionadded:: 3.3 + .. versionchanged:: 3.6 + When the metaclass does not have a ``__prepare__`` attribute, + ``prepare_class`` defaults to returning a new + :class:`collections.OrderedDict` instance instead of :func:`dict`. + .. seealso:: :ref:`metaclasses` diff --git a/Doc/reference/compound_stmts.rst b/Doc/reference/compound_stmts.rst --- a/Doc/reference/compound_stmts.rst +++ b/Doc/reference/compound_stmts.rst @@ -630,6 +630,14 @@ dictionary. The class name is bound to this class object in the original local namespace. +The order in which non-special attributes were defined in the class body +is preserved in the ``__definition_order__`` attribute on the new class. +If that order is not known then the attribute is set to ``None``. + +.. versionchanged:: 3.6 + + Add ``__definition_order__`` to classes. + Class creation can be customized heavily using :ref:`metaclasses `. Classes can also be decorated: just like when decorating functions, :: diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1692,7 +1692,10 @@ additional keyword arguments, if any, come from the class definition). If the metaclass has no ``__prepare__`` attribute, then the class namespace -is initialised as an empty :func:`dict` instance. +is initialised as an empty :class:`collections.OrderedDict` instance. + +.. versionchanged:: 3.6 + Defaults to :class:`collections.OrderedDict` instead of :func:`dict`. .. seealso:: diff --git a/Include/odictobject.h b/Include/odictobject.h --- a/Include/odictobject.h +++ b/Include/odictobject.h @@ -28,6 +28,8 @@ PyAPI_FUNC(int) PyODict_SetItem(PyObject *od, PyObject *key, PyObject *item); PyAPI_FUNC(int) PyODict_DelItem(PyObject *od, PyObject *key); +PyAPI_FUNC(PyObject *) _PyODict_KeysAsTuple(PyObject *od); + /* wrappers around PyDict* functions */ #define PyODict_GetItem(od, key) PyDict_GetItem((PyObject *)od, key) #define PyODict_GetItemWithError(od, key) \ diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1777,6 +1777,66 @@ A.__doc__ = doc self.assertEqual(A.__doc__, doc) + def test_type_definition_order(self): + class Spam: + b = 1 + c = 3 + a = 2 + d = 4 + eggs = 2 + e = 5 + b = 42 + + self.assertEqual(Spam.__definition_order__, + ('b', 'c', 'a', 'd', 'eggs', 'e')) + with self.assertRaises(AttributeError): + Spam.__definition_order__ = ('x', 'y', 'z') + with self.assertRaises(AttributeError): + Spam.__definition_order__ = None + with self.assertRaises(AttributeError): + del Spam.__definition_order__ + + self.assertEqual(object.__definition_order__, None) + self.assertEqual(type.__definition_order__, None) + self.assertEqual(dict.__definition_order__, None) + self.assertEqual(type(None).__definition_order__, None) + + class Empty: + pass + + self.assertEqual(Empty.__definition_order__, tuple()) + + class Dunder: + VAR = 3 + def __init__(self): + pass + + self.assertEqual(Dunder.__definition_order__, ('VAR',)) + + class DunderOnly: + __xyz__ = None + def __init__(self): + pass + + self.assertEqual(DunderOnly.__definition_order__, tuple()) + + class Overwritten: + __definition_order__ = None + a = 1 + b = 2 + c = 3 + + self.assertEqual(Overwritten.__definition_order__, None) + + class Meta(type): + def __prepare__(self, *args, **kwargs): + return {} + + class NotODict(metaclass=Meta): + x='y' + + self.assertEqual(NotODict.__definition_order__, None) + def test_bad_args(self): with self.assertRaises(TypeError): type() diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4580,7 +4580,8 @@ self.assertNotIsInstance(it, list) keys = list(it) keys.sort() - self.assertEqual(keys, ['__dict__', '__doc__', '__module__', + self.assertEqual(keys, ['__definition_order__', + '__dict__', '__doc__', '__module__', '__weakref__', 'meth']) @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), @@ -4590,7 +4591,7 @@ it = self.C.__dict__.values() self.assertNotIsInstance(it, list) values = list(it) - self.assertEqual(len(values), 5) + self.assertEqual(len(values), 6) @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') @@ -4600,7 +4601,8 @@ self.assertNotIsInstance(it, list) keys = [item[0] for item in it] keys.sort() - self.assertEqual(keys, ['__dict__', '__doc__', '__module__', + self.assertEqual(keys, ['__definition_order__', + '__dict__', '__doc__', '__module__', '__weakref__', 'meth']) def test_dict_type_with_metaclass(self): diff --git a/Lib/test/test_metaclass.py b/Lib/test/test_metaclass.py --- a/Lib/test/test_metaclass.py +++ b/Lib/test/test_metaclass.py @@ -180,7 +180,7 @@ meta: C () ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)] kw: [] - >>> type(C) is dict + >>> type(C) is OrderedDict True >>> print(sorted(C.items())) [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)] @@ -212,7 +212,7 @@ The default metaclass must define a __prepare__() method. >>> type.__prepare__() - {} + OrderedDict() >>> Make sure it works with subclassing. @@ -248,6 +248,7 @@ """ +from collections import OrderedDict import sys # Trace function introduces __locals__ which causes various tests to fail. diff --git a/Lib/test/test_pydoc.py b/Lib/test/test_pydoc.py --- a/Lib/test/test_pydoc.py +++ b/Lib/test/test_pydoc.py @@ -70,6 +70,11 @@ | __dict__%s |\x20\x20 | __weakref__%s + |\x20\x20 + | ---------------------------------------------------------------------- + | Data and other attributes defined here: + |\x20\x20 + | __definition_order__ = ('__doc__', '__init__') \x20\x20\x20\x20 class B(builtins.object) | Data descriptors defined here: @@ -82,6 +87,8 @@ | Data and other attributes defined here: |\x20\x20 | NO_MEANING = 'eggs' + |\x20\x20 + | __definition_order__ = ('NO_MEANING',) \x20\x20\x20\x20 class C(builtins.object) | Methods defined here: @@ -102,6 +109,11 @@ |\x20\x20 | __weakref__ | list of weak references to the object (if defined) + |\x20\x20 + | ---------------------------------------------------------------------- + | Data and other attributes defined here: + |\x20\x20 + | __definition_order__ = ('say_no', 'get_answer', 'is_it_true') FUNCTIONS doc_func() @@ -176,6 +188,10 @@
__weakref__
%s
+
+Data and other attributes defined here:
+
__definition_order__ = ('__doc__', '__init__')
+

@@ -194,6 +210,8 @@ Data and other attributes defined here:
NO_MEANING = 'eggs'
+
__definition_order__ = ('NO_MEANING',)
+

@@ -216,6 +234,10 @@
__weakref__
list of weak references to the object (if defined)
+
+Data and other attributes defined here:
+
__definition_order__ = ('say_no', 'get_answer', 'is_it_true')
+

@@ -277,6 +299,11 @@ | ham |\x20\x20 | ---------------------------------------------------------------------- + | Data and other attributes defined here: + |\x20\x20 + | __definition_order__ = ('ham',) + |\x20\x20 + | ---------------------------------------------------------------------- | Data and other attributes inherited from Meta: |\x20\x20 | ham = 'spam' @@ -418,6 +445,7 @@ expected_html = expected_html_pattern % ( (mod_url, mod_file, doc_loc) + expected_html_data_docstrings) + self.maxDiff = None self.assertEqual(result, expected_html) @unittest.skipIf(sys.flags.optimize >= 2, @@ -465,7 +493,9 @@ pass adoc = pydoc.render_doc(A()) bdoc = pydoc.render_doc(B()) - self.assertEqual(adoc.replace("A", "B"), bdoc) + self.maxDiff = None + expected = adoc.replace("A", "B").replace("'__name__',", "") + self.assertEqual(bdoc, expected) def test_not_here(self): missing_module = "test.i_am_not_here" diff --git a/Lib/types.py b/Lib/types.py --- a/Lib/types.py +++ b/Lib/types.py @@ -28,6 +28,12 @@ def _m(self): pass MethodType = type(_C()._m) +# This should end up as OrderedDict. +_DefaultClassNamespaceType = None +class _: + global _DefaultClassNamespaceType + _DefaultClassNamespaceType = type(locals()) + BuiltinFunctionType = type(len) BuiltinMethodType = type([].append) # Same as BuiltinFunctionType @@ -85,7 +91,7 @@ if hasattr(meta, '__prepare__'): ns = meta.__prepare__(name, bases, **kwds) else: - ns = {} + ns = _DefaultClassNamespaceType() return meta, ns, kwds def _calculate_meta(meta, bases): diff --git a/Lib/typing.py b/Lib/typing.py --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1298,6 +1298,7 @@ if (not attr.startswith('_abc_') and attr != '__abstractmethods__' and attr != '_is_protocol' and + attr != '__definition_order__' and attr != '__dict__' and attr != '__args__' and attr != '__slots__' and diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -161,6 +161,9 @@ - Issue #27147: Mention PEP 420 in the importlib docs. +- Issue #24254: Use OrderedDict as the default class definition namespace + and expose the definition order (type.__definition_order__). + - Issue #25339: PYTHONIOENCODING now has priority over locale in setting the error handler for stdin and stdout. @@ -179,6 +182,9 @@ - Issue #21099: Switch applicable importlib tests to use PEP 451 API. +- Issue #24254: Default to using OrderedDict in the class *definition* + namespace. + - Issue #26563: Debug hooks on Python memory allocators now raise a fatal error if functions of the :c:func:`PyMem_Malloc` family are called without holding the GIL. diff --git a/Objects/odictobject.c b/Objects/odictobject.c --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -1762,6 +1762,21 @@ return _PyDict_DelItem_KnownHash(od, key, hash); }; +PyObject * +_PyODict_KeysAsTuple(PyObject *od) { + Py_ssize_t i = 0; + _ODictNode *node; + PyObject *keys = PyTuple_New(PyODict_Size(od)); + if (keys == NULL) + return NULL; + _odict_FOREACH((PyODictObject *)od, node) { + Py_INCREF(_odictnode_KEY(node)); + PyTuple_SET_ITEM(keys, i, _odictnode_KEY(node)); + i++; + } + return keys; +} + /* ------------------------------------------- * The OrderedDict views (keys/values/items) diff --git a/Objects/typeobject.c b/Objects/typeobject.c --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -48,6 +48,7 @@ _Py_IDENTIFIER(__abstractmethods__); _Py_IDENTIFIER(__class__); _Py_IDENTIFIER(__delitem__); +_Py_IDENTIFIER(__definition_order__); _Py_IDENTIFIER(__dict__); _Py_IDENTIFIER(__doc__); _Py_IDENTIFIER(__getattribute__); @@ -487,6 +488,23 @@ } static PyObject * +type_deforder(PyTypeObject *type, void *context) +{ + if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) { + PyObject *order = _PyDict_GetItemId(type->tp_dict, &PyId___definition_order__); + if (!order) { + PyErr_Format(PyExc_AttributeError, "__definition_order__"); + return NULL; + } + Py_INCREF(order); + return order; + } + else { + Py_RETURN_NONE; + } +} + +static PyObject * type_abstractmethods(PyTypeObject *type, void *context) { PyObject *mod = NULL; @@ -832,6 +850,7 @@ {"__qualname__", (getter)type_qualname, (setter)type_set_qualname, NULL}, {"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL}, {"__module__", (getter)type_module, (setter)type_set_module, NULL}, + {"__definition_order__", (getter)type_deforder, NULL, NULL}, {"__abstractmethods__", (getter)type_abstractmethods, (setter)type_set_abstractmethods, NULL}, {"__dict__", (getter)type_dict, NULL, NULL}, @@ -1414,6 +1433,29 @@ return lookup_maybe(self, attrid); } +int +is_special(PyObject *name, PyObject *dunder) +{ + Py_ssize_t matched; + + if (PyUnicode_GET_LENGTH(name) < 4) + return 0; + + matched = PyUnicode_Tailmatch(name, dunder, 0, PY_SSIZE_T_MAX, -1); + if (matched < 0) + return -1; + if (matched == 0) + return 0; + + matched = PyUnicode_Tailmatch(name, dunder, 0, PY_SSIZE_T_MAX, 1); + if (matched < 0) + return -1; + if (matched == 0) + return 0; + + return 1; +} + /* A variation of PyObject_CallMethod that uses lookup_method() instead of PyObject_GetAttrString(). This uses the same convention as lookup_method to cache the interned name string object. */ @@ -2270,7 +2312,7 @@ { PyObject *name, *bases = NULL, *orig_dict, *dict = NULL; static char *kwlist[] = {"name", "bases", "dict", 0}; - PyObject *qualname, *slots = NULL, *tmp, *newslots; + PyObject *qualname, *slots = NULL, *tmp, *newslots, *deforder; PyTypeObject *type = NULL, *base, *tmptype, *winner; PyHeapTypeObject *et; PyMemberDef *mp; @@ -2339,10 +2381,65 @@ goto error; } + /* Copy the definition namespace into a new dict. */ dict = PyDict_Copy(orig_dict); if (dict == NULL) goto error; + /* Record the definition order of the class's namespace. */ + if (!PyMapping_HasKey(orig_dict, PyId___definition_order__.object)) { + if (PyODict_Check(orig_dict)) { + int special; + PyObject *names, *name; + Py_ssize_t numnames = PyODict_Size(orig_dict); + Py_ssize_t numdunder = 0; + PyObject *dunder; + + names = _PyODict_KeysAsTuple(orig_dict); + if (names == NULL) + goto error; + deforder = PyTuple_New(numnames); + if (deforder == NULL) { + Py_DECREF(names); + goto error; + } + + /* Remove "dunder" names from the definition order. */ + dunder = PyUnicode_InternFromString("__"); + if (!dunder) { + Py_DECREF(names); + Py_DECREF(deforder); + goto error; + } + for (i = 0; i < numnames; i++) { + name = PyTuple_GET_ITEM(names, i); + special = is_special(name, dunder); + if (special < 0) { + Py_DECREF(names); + Py_DECREF(deforder); + goto error; + } + if (special) + numdunder += 1; + else + PyTuple_SET_ITEM(deforder, i-numdunder, name); + } + Py_DECREF(names); + if (_PyTuple_Resize(&deforder, numnames-numdunder)) { + Py_DECREF(deforder); + goto error; + } + } else { + deforder = Py_None; + Py_INCREF(Py_None); + } + if (_PyDict_SetItemId(dict, + &PyId___definition_order__, deforder) != 0) { + Py_DECREF(deforder); + goto error; + } + } + /* Check for a __slots__ sequence variable in dict, and count it */ slots = _PyDict_GetItemId(dict, &PyId___slots__); nslots = 0; @@ -3081,7 +3178,7 @@ static PyObject * type_prepare(PyObject *self, PyObject *args, PyObject *kwds) { - return PyDict_New(); + return PyODict_New(); } /* diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -145,7 +145,7 @@ if (prep == NULL) { if (PyErr_ExceptionMatches(PyExc_AttributeError)) { PyErr_Clear(); - ns = PyDict_New(); + ns = PyODict_New(); } else { Py_DECREF(meta);