]> granicus.if.org Git - python/commitdiff
Issue #24254: Preserve class attribute definition order.
authorEric Snow <ericsnowcurrently@gmail.com>
Mon, 5 Sep 2016 21:50:11 +0000 (14:50 -0700)
committerEric Snow <ericsnowcurrently@gmail.com>
Mon, 5 Sep 2016 21:50:11 +0000 (14:50 -0700)
18 files changed:
Doc/library/inspect.rst
Doc/library/types.rst
Doc/reference/compound_stmts.rst
Doc/reference/datamodel.rst
Doc/whatsnew/3.6.rst
Include/object.h
Include/odictobject.h
Lib/test/test_builtin.py
Lib/test/test_metaclass.py
Lib/test/test_pydoc.py
Lib/test/test_sys.py
Lib/test/test_types.py
Lib/types.py
Lib/typing.py
Misc/NEWS
Objects/odictobject.c
Objects/typeobject.c
Python/bltinmodule.c

index 5cb7c22adbeddfea588d15d91601fbf9e91cc1ad..1977d88a2b5cfb1dd771d8e98880521cf85e022f 100644 (file)
@@ -34,185 +34,190 @@ provided as convenient choices for the second argument to :func:`getmembers`.
 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      |
++-----------+----------------------+---------------------------+
+|           | __name__             | name with which this      |
+|           |                      | class was defined         |
++-----------+----------------------+---------------------------+
+|           | __qualname__         | qualified name            |
++-----------+----------------------+---------------------------+
+|           | __module__           | name of module in which   |
+|           |                      | this class was defined    |
++-----------+----------------------+---------------------------+
+|           | __definition_order__ | the names of the class's  |
+|           |                      | attributes, in the order  |
+|           |                      | in which they were        |
+|           |                      | defined (if known)        |
++-----------+----------------------+---------------------------+
+| 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
 
@@ -221,6 +226,10 @@ attributes:
    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])
 
index 118bc4c29d85e7dc358a9fccb503aafac0e0405f..5eb84c1886c86a039382a984415a7079859b469e 100644 (file)
@@ -53,8 +53,20 @@ Dynamic Type Creation
    in *kwds* argument with any ``'metaclass'`` entry removed. If no *kwds*
    argument is passed in, this will be an empty dict.
 
+   .. impl-detail::
+
+      CPython uses :class:`collections.OrderedDict` for the default
+      namespace.
+
    .. versionadded:: 3.3
 
+   .. versionchanged:: 3.6
+
+      The default value for the ``namespace`` element of the returned
+      tuple has changed from :func:`dict`.  Now an insertion-order-
+      preserving mapping is used when the metaclass does not have a
+      ``__prepare__`` method,
+
 .. seealso::
 
    :ref:`metaclasses`
index 1c5bbdf24ae1cb32137e98b0b6f0ea2f428c556e..2eab29a8e09185f7331896f636700dc5b19e3edc 100644 (file)
@@ -632,6 +632,17 @@ list for the base classes and the saved local namespace for the attribute
 dictionary.  The class name is bound to this class object in the original local
 namespace.
 
+The order in which attributes are 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 :const:`None`.  The class body
+may include a ``__definition_order__`` attribute.  In that case it is used
+directly.  The value must be a tuple of identifiers or ``None``, otherwise
+:exc:`TypeError` will be raised when the class statement is executed.
+
+.. versionchanged:: 3.6
+
+   Add ``__definition_order__`` to classes.
+
 Class creation can be customized heavily using :ref:`metaclasses <metaclasses>`.
 
 Classes can also be decorated: just like when decorating functions, ::
index e7328ab2115a9b8ed1c223f247cbf14f114e5cc7..00785ed39c64f70bb9ddff8a8e9f6b251e1689f2 100644 (file)
@@ -1750,7 +1750,14 @@ as ``namespace = metaclass.__prepare__(name, bases, **kwds)`` (where the
 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 ordered mapping.
+
+.. impl-detail::
+
+   In CPython the default is :class:`collections.OrderedDict`.
+
+.. versionchanged:: 3.6
+   Defaults to :class:`collections.OrderedDict` instead of :func:`dict`.
 
 .. seealso::
 
index e560fba3378e42a3b45b9617d619695ebb27e1bc..031bf797b8f6519378eae7b55050698a012938ac 100644 (file)
@@ -92,6 +92,7 @@ Windows improvements:
     :pep:`4XX` - Python Virtual Environments
        PEP written by Carl Meyer
 
+.. XXX PEP 520: :ref:`Preserving Class Attribute Definition Order<whatsnew-deforder>`
 
 New Features
 ============
@@ -271,6 +272,31 @@ Example of fatal error on buffer overflow using
 (Contributed by Victor Stinner in :issue:`26516` and :issue:`26564`.)
 
 
+.. _whatsnew-deforder:
+
+PEP 520: Preserving Class Attribute Definition Order
+----------------------------------------------------
+
+Attributes in a class definition body have a natural ordering: the same
+order in which the names appear in the source.  This order is now
+preserved in the new class's ``__definition_order__`` attribute.  It is
+a tuple of the attribute names, in the order in which they appear in
+the class definition body.
+
+For types that don't have a definition (e.g. builtins), or the attribute
+order could not be determined, ``__definition_order__`` is ``None``.
+
+Also, the effective default class *execution* namespace (returned from
+``type.__prepare__()``) is now an insertion-order-preserving mapping.
+For CPython, it is now ``collections.OrderedDict``.  Note that the
+class namespace, ``cls.__dict__``, is unchanged.
+
+.. seealso::
+
+   :pep:`520` - Preserving Class Attribute Definition Order
+      PEP written and implemented by Eric Snow.
+
+
 Other Language Changes
 ======================
 
index 0c88603e3f2aa359c124a120b3f3d1b0a81635fe..85bfce36908a48f415a7c2c3a062a4dfd4fead6a 100644 (file)
@@ -421,6 +421,8 @@ typedef struct _typeobject {
 
     destructor tp_finalize;
 
+    PyObject *tp_deforder;
+
 #ifdef COUNT_ALLOCS
     /* these must be last and never explicitly initialized */
     Py_ssize_t tp_allocs;
index c1d9592a1db861863f4d5a9683d9530039bebba6..ca865c735624e50cbced93341162f2da3a0e3b2e 100644 (file)
@@ -28,6 +28,10 @@ PyAPI_FUNC(PyObject *) PyODict_New(void);
 PyAPI_FUNC(int) PyODict_SetItem(PyObject *od, PyObject *key, PyObject *item);
 PyAPI_FUNC(int) PyODict_DelItem(PyObject *od, PyObject *key);
 
+#ifndef Py_LIMITED_API
+PyAPI_FUNC(PyObject *) _PyODict_KeysAsTuple(PyObject *od);
+#endif
+
 /* wrappers around PyDict* functions */
 #define PyODict_GetItem(od, key) PyDict_GetItem((PyObject *)od, key)
 #define PyODict_GetItemWithError(od, key) \
index 7741a7911dea1cfed09e7678dc25031d38815f78..486f445069196fc2da388fb4cbc2f7abfa1291f1 100644 (file)
@@ -16,8 +16,10 @@ import traceback
 import types
 import unittest
 import warnings
+from collections import OrderedDict
 from operator import neg
-from test.support import TESTFN, unlink,  run_unittest, check_warnings
+from test.support import (TESTFN, unlink,  run_unittest, check_warnings,
+                          cpython_only)
 from test.support.script_helper import assert_python_ok
 try:
     import pty, signal
@@ -1778,6 +1780,194 @@ class TestType(unittest.TestCase):
             A.__doc__ = doc
             self.assertEqual(A.__doc__, doc)
 
+    def test_type_definition_order_nonempty(self):
+        class Spam:
+            b = 1
+            c = 3
+            a = 2
+            d = 4
+            eggs = 2
+            e = 5
+            b = 42
+
+        self.assertEqual(Spam.__definition_order__,
+                         ('__module__', '__qualname__',
+                          'b', 'c', 'a', 'd', 'eggs', 'e'))
+
+    def test_type_definition_order_empty(self):
+        class Empty:
+            pass
+
+        self.assertEqual(Empty.__definition_order__,
+                         ('__module__', '__qualname__'))
+
+    def test_type_definition_order_on_instance(self):
+        class Spam:
+            a = 2
+            b = 1
+            c = 3
+        with self.assertRaises(AttributeError):
+            Spam().__definition_order__
+
+    def test_type_definition_order_set_to_None(self):
+        class Spam:
+            a = 2
+            b = 1
+            c = 3
+        Spam.__definition_order__ = None
+        self.assertEqual(Spam.__definition_order__, None)
+
+    def test_type_definition_order_set_to_tuple(self):
+        class Spam:
+            a = 2
+            b = 1
+            c = 3
+        Spam.__definition_order__ = ('x', 'y', 'z')
+        self.assertEqual(Spam.__definition_order__, ('x', 'y', 'z'))
+
+    def test_type_definition_order_deleted(self):
+        class Spam:
+            a = 2
+            b = 1
+            c = 3
+        del Spam.__definition_order__
+        self.assertEqual(Spam.__definition_order__, None)
+
+    def test_type_definition_order_set_to_bad_type(self):
+        class Spam:
+            a = 2
+            b = 1
+            c = 3
+        Spam.__definition_order__ = 42
+        self.assertEqual(Spam.__definition_order__, 42)
+
+    def test_type_definition_order_builtins(self):
+        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)
+
+    def test_type_definition_order_dunder_names_included(self):
+        class Dunder:
+            VAR = 3
+            def __init__(self):
+                pass
+
+        self.assertEqual(Dunder.__definition_order__,
+                         ('__module__', '__qualname__',
+                          'VAR', '__init__'))
+
+    def test_type_definition_order_only_dunder_names(self):
+        class DunderOnly:
+            __xyz__ = None
+            def __init__(self):
+                pass
+
+        self.assertEqual(DunderOnly.__definition_order__,
+                         ('__module__', '__qualname__',
+                          '__xyz__', '__init__'))
+
+    def test_type_definition_order_underscore_names(self):
+        class HalfDunder:
+            __whether_to_be = True
+            or_not_to_be__ = False
+
+        self.assertEqual(HalfDunder.__definition_order__,
+                         ('__module__', '__qualname__',
+                          '_HalfDunder__whether_to_be', 'or_not_to_be__'))
+
+    def test_type_definition_order_with_slots(self):
+        class Slots:
+            __slots__ = ('x', 'y')
+            a = 1
+            b = 2
+
+        self.assertEqual(Slots.__definition_order__,
+                         ('__module__', '__qualname__',
+                          '__slots__', 'a', 'b'))
+
+    def test_type_definition_order_overwritten_None(self):
+        class OverwrittenNone:
+            __definition_order__ = None
+            a = 1
+            b = 2
+            c = 3
+
+        self.assertEqual(OverwrittenNone.__definition_order__, None)
+
+    def test_type_definition_order_overwritten_tuple(self):
+        class OverwrittenTuple:
+            __definition_order__ = ('x', 'y', 'z')
+            a = 1
+            b = 2
+            c = 3
+
+        self.assertEqual(OverwrittenTuple.__definition_order__,
+                         ('x', 'y', 'z'))
+
+    def test_type_definition_order_overwritten_bad_item(self):
+        with self.assertRaises(TypeError):
+            class PoorlyOverwritten:
+                __definition_order__ = ('a', 2, 'c')
+                a = 1
+                b = 2
+                c = 3
+
+    def test_type_definition_order_overwritten_bad_type(self):
+        with self.assertRaises(TypeError):
+            class PoorlyOverwritten:
+                __definition_order__ = ['a', 2, 'c']
+                a = 1
+                b = 2
+                c = 3
+
+    def test_type_definition_order_metaclass(self):
+        class Meta(type):
+            SPAM = 42
+
+            def __init__(self, *args, **kwargs):
+                super().__init__(*args, **kwargs)
+
+        self.assertEqual(Meta.__definition_order__,
+                         ('__module__', '__qualname__',
+                          'SPAM', '__init__'))
+
+    def test_type_definition_order_OrderedDict(self):
+        class Meta(type):
+            def __prepare__(self, *args, **kwargs):
+                return OrderedDict()
+
+        class WithODict(metaclass=Meta):
+            x='y'
+
+        self.assertEqual(WithODict.__definition_order__,
+                         ('__module__', '__qualname__', 'x'))
+
+        class Meta(type):
+            def __prepare__(self, *args, **kwargs):
+                class ODictSub(OrderedDict):
+                    pass
+                return ODictSub()
+
+        class WithODictSub(metaclass=Meta):
+            x='y'
+
+        self.assertEqual(WithODictSub.__definition_order__,
+                         ('__module__', '__qualname__', 'x'))
+
+    @cpython_only
+    def test_type_definition_order_cpython(self):
+        # some implementations will have an ordered-by-default dict.
+
+        class Meta(type):
+            def __prepare__(self, *args, **kwargs):
+                return {}
+
+        class NotOrdered(metaclass=Meta):
+            x='y'
+
+        self.assertEqual(NotOrdered.__definition_order__, None)
+
     def test_bad_args(self):
         with self.assertRaises(TypeError):
             type()
index e6fe20a107c239a64fc1e8bedfda2b159134f56f..4db792ecef44c5239d67881941efb9355d34214e 100644 (file)
@@ -180,7 +180,7 @@ Use a metaclass that doesn't derive from type.
     meta: C ()
     ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
     kw: []
-    >>> type(C) is dict
+    >>> type(C) is types._DefaultClassNamespaceType
     True
     >>> print(sorted(C.items()))
     [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
@@ -211,8 +211,11 @@ And again, with a __prepare__ attribute.
 
 The default metaclass must define a __prepare__() method.
 
-    >>> type.__prepare__()
-    {}
+    >>> ns = type.__prepare__()
+    >>> type(ns) is types._DefaultClassNamespaceType
+    True
+    >>> list(ns) == []
+    True
     >>>
 
 Make sure it works with subclassing.
@@ -248,7 +251,9 @@ Test failures in looking up the __prepare__ method work.
 
 """
 
+from collections import OrderedDict
 import sys
+import types
 
 # Trace function introduces __locals__ which causes various tests to fail.
 if hasattr(sys, 'gettrace') and sys.gettrace():
index 4998597e21655b654f365846335d24618f26b8fd..527234bc6ec114d7d89008968f08a1fae78a8c7f 100644 (file)
@@ -427,6 +427,7 @@ class PydocDocTest(unittest.TestCase):
         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,
@@ -473,13 +474,18 @@ class PydocDocTest(unittest.TestCase):
     def test_non_str_name(self):
         # issue14638
         # Treat illegal (non-str) name like no name
+        # Definition order is set to None so it looks the same in both
+        # cases.
         class A:
+            __definition_order__ = None
             __name__ = 42
         class B:
             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")
+        self.assertEqual(bdoc, expected)
 
     def test_not_here(self):
         missing_module = "test.i_am_not_here"
index 4435d6995b71b55ccfdba8fdc151d6b25bad321a..3131f367cca7aa3d236bf4bdc37611d062d9003c 100644 (file)
@@ -1084,7 +1084,7 @@ class SizeofTest(unittest.TestCase):
         check((1,2,3), vsize('') + 3*self.P)
         # type
         # static type: PyTypeObject
-        fmt = 'P2n15Pl4Pn9Pn11PIP'
+        fmt = 'P2n15Pl4Pn9Pn11PIPP'
         if hasattr(sys, 'getcounts'):
             fmt += '3n2P'
         s = vsize(fmt)
index a202196bd2fcfa8c7f05f3739045bc3c25adcdcd..e5e110f9c2fd5c096aa034e132d3dcdea4024370 100644 (file)
@@ -825,6 +825,28 @@ class ClassCreationTests(unittest.TestCase):
         self.assertEqual(C.y, 1)
         self.assertEqual(C.z, 2)
 
+    def test_new_class_deforder(self):
+        C = types.new_class("C")
+        self.assertEqual(C.__definition_order__, tuple())
+
+        Meta = self.Meta
+        def func(ns):
+            ns["x"] = 0
+        D = types.new_class("D", (), {"metaclass": Meta, "z": 2}, func)
+        self.assertEqual(D.__definition_order__, ('y', 'z', 'x'))
+
+        def func(ns):
+            ns["__definition_order__"] = None
+            ns["x"] = 0
+        D = types.new_class("D", (), {"metaclass": Meta, "z": 2}, func)
+        self.assertEqual(D.__definition_order__, None)
+
+        def func(ns):
+            ns["__definition_order__"] = ('a', 'b', 'c')
+            ns["x"] = 0
+        D = types.new_class("D", (), {"metaclass": Meta, "z": 2}, func)
+        self.assertEqual(D.__definition_order__, ('a', 'b', 'c'))
+
     # Many of the following tests are derived from test_descr.py
     def test_prepare_class(self):
         # Basic test of metaclass derivation
index 48891cd3f60dab5738c8aff627d094a4905bdbb3..cc093cb403d32cf7037b656709e3c222a1dae20e 100644 (file)
@@ -25,8 +25,11 @@ CoroutineType = type(_c)
 _c.close()  # Prevent ResourceWarning
 
 class _C:
+    _nsType = type(locals())
     def _m(self): pass
 MethodType = type(_C()._m)
+# In CPython, this should end up as OrderedDict.
+_DefaultClassNamespaceType = _C._nsType
 
 BuiltinFunctionType = type(len)
 BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
@@ -85,7 +88,7 @@ def prepare_class(name, bases=(), kwds=None):
     if hasattr(meta, '__prepare__'):
         ns = meta.__prepare__(name, bases, **kwds)
     else:
-        ns = {}
+        ns = _DefaultClassNamespaceType()
     return meta, ns, kwds
 
 def _calculate_meta(meta, bases):
index 5573a1fbf93ccc122d8e73223dac99afbc86fe91..5f451b0ca18060b9e8528662a1ed88ebee486f56 100644 (file)
@@ -1301,6 +1301,7 @@ class _ProtocolMeta(GenericMeta):
                     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
index 428cf2fd9e70282f0f83d4ea5cb4be24bdcb9cbd..82e4c418105385ee922f1c23595dc51a12e57b05 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -49,6 +49,8 @@ Core and Builtins
   potentially have caused off-by-one-ulp results on platforms with
   unreliable ldexp implementations.
 
+- Issue #24254: Make class definition namespace ordered by default.
+
 - Issue #27662: Fix an overflow check in ``List_New``: the original code was
   checking against ``Py_SIZE_MAX`` instead of the correct upper bound of
   ``Py_SSIZE_T_MAX``. Patch by Xiang Zhang.
index 14be1cd24a461aa9635ef1169c734cb629c15283..f0560749be87496e585854416ea59d69044d4f17 100644 (file)
@@ -1762,6 +1762,21 @@ PyODict_DelItem(PyObject *od, PyObject *key)
     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)
index 5f0db2b0052d141722ffba5060965e07e923befb..6cffb4e8ffadf343685a0fa95050d4622695c5f8 100644 (file)
@@ -48,6 +48,7 @@ static size_t method_cache_collisions = 0;
 _Py_IDENTIFIER(__abstractmethods__);
 _Py_IDENTIFIER(__class__);
 _Py_IDENTIFIER(__delitem__);
+_Py_IDENTIFIER(__definition_order__);
 _Py_IDENTIFIER(__dict__);
 _Py_IDENTIFIER(__doc__);
 _Py_IDENTIFIER(__getattribute__);
@@ -488,6 +489,23 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
     return _PyDict_SetItemId(type->tp_dict, &PyId___module__, value);
 }
 
+static PyObject *
+type_deforder(PyTypeObject *type, void *context)
+{
+    if (type->tp_deforder == NULL)
+        Py_RETURN_NONE;
+    Py_INCREF(type->tp_deforder);
+    return type->tp_deforder;
+}
+
+static int
+type_set_deforder(PyTypeObject *type, PyObject *value, void *context)
+{
+    Py_XINCREF(value);
+    Py_XSETREF(type->tp_deforder, value);
+    return 0;
+}
+
 static PyObject *
 type_abstractmethods(PyTypeObject *type, void *context)
 {
@@ -834,6 +852,8 @@ static PyGetSetDef type_getsets[] = {
     {"__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,
+     (setter)type_set_deforder, NULL},
     {"__abstractmethods__", (getter)type_abstractmethods,
      (setter)type_set_abstractmethods, NULL},
     {"__dict__",  (getter)type_dict,  NULL, NULL},
@@ -2351,6 +2371,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
         goto error;
     }
 
+    /* Copy the definition namespace into a new dict. */
     dict = PyDict_Copy(orig_dict);
     if (dict == NULL)
         goto error;
@@ -2559,6 +2580,48 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
     if (qualname != NULL && PyDict_DelItem(dict, PyId___qualname__.object) < 0)
         goto error;
 
+    /* Set tp_deforder to the extracted definition order, if any. */
+    type->tp_deforder = _PyDict_GetItemId(dict, &PyId___definition_order__);
+    if (type->tp_deforder != NULL) {
+        Py_INCREF(type->tp_deforder);
+
+        // Due to subclass lookup, __definition_order__ can't be in __dict__.
+        if (_PyDict_DelItemId(dict, &PyId___definition_order__) != 0) {
+            goto error;
+        }
+
+        if (type->tp_deforder != Py_None) {
+            Py_ssize_t numnames;
+
+            if (!PyTuple_Check(type->tp_deforder)) {
+                PyErr_SetString(PyExc_TypeError,
+                                "__definition_order__ must be a tuple or None");
+                goto error;
+            }
+
+            // Make sure they are identifers.
+            numnames = PyTuple_Size(type->tp_deforder);
+            for (i = 0; i < numnames; i++) {
+                PyObject *name = PyTuple_GET_ITEM(type->tp_deforder, i);
+                if (name == NULL) {
+                    goto error;
+                }
+                if (!PyUnicode_Check(name) || !PyUnicode_IsIdentifier(name)) {
+                    PyErr_Format(PyExc_TypeError,
+                                 "__definition_order__ must "
+                                 "contain only identifiers, got '%s'",
+                                 name);
+                    goto error;
+                }
+            }
+        }
+    }
+    else if (PyODict_Check(orig_dict)) {
+        type->tp_deforder = _PyODict_KeysAsTuple(orig_dict);
+        if (type->tp_deforder == NULL)
+            goto error;
+    }
+
     /* Set tp_doc to a copy of dict['__doc__'], if the latter is there
        and is a string.  The __doc__ accessor will first look for tp_doc;
        if that fails, it will still look into __dict__.
@@ -3073,6 +3136,7 @@ type_dealloc(PyTypeObject *type)
     Py_XDECREF(type->tp_mro);
     Py_XDECREF(type->tp_cache);
     Py_XDECREF(type->tp_subclasses);
+    Py_XDECREF(type->tp_deforder);
     /* A type's tp_doc is heap allocated, unlike the tp_doc slots
      * of most other objects.  It's okay to cast it to char *.
      */
@@ -3115,7 +3179,7 @@ type_subclasses(PyTypeObject *type, PyObject *args_ignored)
 static PyObject *
 type_prepare(PyObject *self, PyObject *args, PyObject *kwds)
 {
-    return PyDict_New();
+    return PyODict_New();
 }
 
 /*
index dc2daa8d1ad135b36a4e7553989151e114e53fec..07d59caba19a91ece065a9e98dd74b007477d81d 100644 (file)
@@ -145,7 +145,7 @@ builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
     if (prep == NULL) {
         if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
             PyErr_Clear();
-            ns = PyDict_New();
+            ns = PyODict_New();
         }
         else {
             Py_DECREF(meta);