Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 92a6c17

Browse files
Issue #24254: Preserve class attribute definition order.
1 parent 4565986 commit 92a6c17

18 files changed

Lines changed: 568 additions & 189 deletions

File tree

Doc/library/inspect.rst

Lines changed: 188 additions & 179 deletions
Large diffs are not rendered by default.

Doc/library/types.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,20 @@ Dynamic Type Creation
5353
in *kwds* argument with any ``'metaclass'`` entry removed. If no *kwds*
5454
argument is passed in, this will be an empty dict.
5555

56+
.. impl-detail::
57+
58+
CPython uses :class:`collections.OrderedDict` for the default
59+
namespace.
60+
5661
.. versionadded:: 3.3
5762

63+
.. versionchanged:: 3.6
64+
65+
The default value for the ``namespace`` element of the returned
66+
tuple has changed from :func:`dict`. Now an insertion-order-
67+
preserving mapping is used when the metaclass does not have a
68+
``__prepare__`` method,
69+
5870
.. seealso::
5971

6072
:ref:`metaclasses`

Doc/reference/compound_stmts.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,17 @@ list for the base classes and the saved local namespace for the attribute
632632
dictionary. The class name is bound to this class object in the original local
633633
namespace.
634634

635+
The order in which attributes are defined in the class body is preserved
636+
in the ``__definition_order__`` attribute on the new class. If that order
637+
is not known then the attribute is set to :const:`None`. The class body
638+
may include a ``__definition_order__`` attribute. In that case it is used
639+
directly. The value must be a tuple of identifiers or ``None``, otherwise
640+
:exc:`TypeError` will be raised when the class statement is executed.
641+
642+
.. versionchanged:: 3.6
643+
644+
Add ``__definition_order__`` to classes.
645+
635646
Class creation can be customized heavily using :ref:`metaclasses <metaclasses>`.
636647

637648
Classes can also be decorated: just like when decorating functions, ::

Doc/reference/datamodel.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1750,7 +1750,14 @@ as ``namespace = metaclass.__prepare__(name, bases, **kwds)`` (where the
17501750
additional keyword arguments, if any, come from the class definition).
17511751

17521752
If the metaclass has no ``__prepare__`` attribute, then the class namespace
1753-
is initialised as an empty :func:`dict` instance.
1753+
is initialised as an empty ordered mapping.
1754+
1755+
.. impl-detail::
1756+
1757+
In CPython the default is :class:`collections.OrderedDict`.
1758+
1759+
.. versionchanged:: 3.6
1760+
Defaults to :class:`collections.OrderedDict` instead of :func:`dict`.
17541761

17551762
.. seealso::
17561763

Doc/whatsnew/3.6.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Windows improvements:
9292
:pep:`4XX` - Python Virtual Environments
9393
PEP written by Carl Meyer
9494

95+
.. XXX PEP 520: :ref:`Preserving Class Attribute Definition Order<whatsnew-deforder>`
9596
9697
New Features
9798
============
@@ -271,6 +272,31 @@ Example of fatal error on buffer overflow using
271272
(Contributed by Victor Stinner in :issue:`26516` and :issue:`26564`.)
272273

273274

275+
.. _whatsnew-deforder:
276+
277+
PEP 520: Preserving Class Attribute Definition Order
278+
----------------------------------------------------
279+
280+
Attributes in a class definition body have a natural ordering: the same
281+
order in which the names appear in the source. This order is now
282+
preserved in the new class's ``__definition_order__`` attribute. It is
283+
a tuple of the attribute names, in the order in which they appear in
284+
the class definition body.
285+
286+
For types that don't have a definition (e.g. builtins), or the attribute
287+
order could not be determined, ``__definition_order__`` is ``None``.
288+
289+
Also, the effective default class *execution* namespace (returned from
290+
``type.__prepare__()``) is now an insertion-order-preserving mapping.
291+
For CPython, it is now ``collections.OrderedDict``. Note that the
292+
class namespace, ``cls.__dict__``, is unchanged.
293+
294+
.. seealso::
295+
296+
:pep:`520` - Preserving Class Attribute Definition Order
297+
PEP written and implemented by Eric Snow.
298+
299+
274300
Other Language Changes
275301
======================
276302

Include/object.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,8 @@ typedef struct _typeobject {
421421

422422
destructor tp_finalize;
423423

424+
PyObject *tp_deforder;
425+
424426
#ifdef COUNT_ALLOCS
425427
/* these must be last and never explicitly initialized */
426428
Py_ssize_t tp_allocs;

Include/odictobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ PyAPI_FUNC(PyObject *) PyODict_New(void);
2828
PyAPI_FUNC(int) PyODict_SetItem(PyObject *od, PyObject *key, PyObject *item);
2929
PyAPI_FUNC(int) PyODict_DelItem(PyObject *od, PyObject *key);
3030

31+
#ifndef Py_LIMITED_API
32+
PyAPI_FUNC(PyObject *) _PyODict_KeysAsTuple(PyObject *od);
33+
#endif
34+
3135
/* wrappers around PyDict* functions */
3236
#define PyODict_GetItem(od, key) PyDict_GetItem((PyObject *)od, key)
3337
#define PyODict_GetItemWithError(od, key) \

Lib/test/test_builtin.py

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
import types
1717
import unittest
1818
import warnings
19+
from collections import OrderedDict
1920
from operator import neg
20-
from test.support import TESTFN, unlink, run_unittest, check_warnings
21+
from test.support import (TESTFN, unlink, run_unittest, check_warnings,
22+
cpython_only)
2123
from test.support.script_helper import assert_python_ok
2224
try:
2325
import pty, signal
@@ -1778,6 +1780,194 @@ def test_type_doc(self):
17781780
A.__doc__ = doc
17791781
self.assertEqual(A.__doc__, doc)
17801782

1783+
def test_type_definition_order_nonempty(self):
1784+
class Spam:
1785+
b = 1
1786+
c = 3
1787+
a = 2
1788+
d = 4
1789+
eggs = 2
1790+
e = 5
1791+
b = 42
1792+
1793+
self.assertEqual(Spam.__definition_order__,
1794+
('__module__', '__qualname__',
1795+
'b', 'c', 'a', 'd', 'eggs', 'e'))
1796+
1797+
def test_type_definition_order_empty(self):
1798+
class Empty:
1799+
pass
1800+
1801+
self.assertEqual(Empty.__definition_order__,
1802+
('__module__', '__qualname__'))
1803+
1804+
def test_type_definition_order_on_instance(self):
1805+
class Spam:
1806+
a = 2
1807+
b = 1
1808+
c = 3
1809+
with self.assertRaises(AttributeError):
1810+
Spam().__definition_order__
1811+
1812+
def test_type_definition_order_set_to_None(self):
1813+
class Spam:
1814+
a = 2
1815+
b = 1
1816+
c = 3
1817+
Spam.__definition_order__ = None
1818+
self.assertEqual(Spam.__definition_order__, None)
1819+
1820+
def test_type_definition_order_set_to_tuple(self):
1821+
class Spam:
1822+
a = 2
1823+
b = 1
1824+
c = 3
1825+
Spam.__definition_order__ = ('x', 'y', 'z')
1826+
self.assertEqual(Spam.__definition_order__, ('x', 'y', 'z'))
1827+
1828+
def test_type_definition_order_deleted(self):
1829+
class Spam:
1830+
a = 2
1831+
b = 1
1832+
c = 3
1833+
del Spam.__definition_order__
1834+
self.assertEqual(Spam.__definition_order__, None)
1835+
1836+
def test_type_definition_order_set_to_bad_type(self):
1837+
class Spam:
1838+
a = 2
1839+
b = 1
1840+
c = 3
1841+
Spam.__definition_order__ = 42
1842+
self.assertEqual(Spam.__definition_order__, 42)
1843+
1844+
def test_type_definition_order_builtins(self):
1845+
self.assertEqual(object.__definition_order__, None)
1846+
self.assertEqual(type.__definition_order__, None)
1847+
self.assertEqual(dict.__definition_order__, None)
1848+
self.assertEqual(type(None).__definition_order__, None)
1849+
1850+
def test_type_definition_order_dunder_names_included(self):
1851+
class Dunder:
1852+
VAR = 3
1853+
def __init__(self):
1854+
pass
1855+
1856+
self.assertEqual(Dunder.__definition_order__,
1857+
('__module__', '__qualname__',
1858+
'VAR', '__init__'))
1859+
1860+
def test_type_definition_order_only_dunder_names(self):
1861+
class DunderOnly:
1862+
__xyz__ = None
1863+
def __init__(self):
1864+
pass
1865+
1866+
self.assertEqual(DunderOnly.__definition_order__,
1867+
('__module__', '__qualname__',
1868+
'__xyz__', '__init__'))
1869+
1870+
def test_type_definition_order_underscore_names(self):
1871+
class HalfDunder:
1872+
__whether_to_be = True
1873+
or_not_to_be__ = False
1874+
1875+
self.assertEqual(HalfDunder.__definition_order__,
1876+
('__module__', '__qualname__',
1877+
'_HalfDunder__whether_to_be', 'or_not_to_be__'))
1878+
1879+
def test_type_definition_order_with_slots(self):
1880+
class Slots:
1881+
__slots__ = ('x', 'y')
1882+
a = 1
1883+
b = 2
1884+
1885+
self.assertEqual(Slots.__definition_order__,
1886+
('__module__', '__qualname__',
1887+
'__slots__', 'a', 'b'))
1888+
1889+
def test_type_definition_order_overwritten_None(self):
1890+
class OverwrittenNone:
1891+
__definition_order__ = None
1892+
a = 1
1893+
b = 2
1894+
c = 3
1895+
1896+
self.assertEqual(OverwrittenNone.__definition_order__, None)
1897+
1898+
def test_type_definition_order_overwritten_tuple(self):
1899+
class OverwrittenTuple:
1900+
__definition_order__ = ('x', 'y', 'z')
1901+
a = 1
1902+
b = 2
1903+
c = 3
1904+
1905+
self.assertEqual(OverwrittenTuple.__definition_order__,
1906+
('x', 'y', 'z'))
1907+
1908+
def test_type_definition_order_overwritten_bad_item(self):
1909+
with self.assertRaises(TypeError):
1910+
class PoorlyOverwritten:
1911+
__definition_order__ = ('a', 2, 'c')
1912+
a = 1
1913+
b = 2
1914+
c = 3
1915+
1916+
def test_type_definition_order_overwritten_bad_type(self):
1917+
with self.assertRaises(TypeError):
1918+
class PoorlyOverwritten:
1919+
__definition_order__ = ['a', 2, 'c']
1920+
a = 1
1921+
b = 2
1922+
c = 3
1923+
1924+
def test_type_definition_order_metaclass(self):
1925+
class Meta(type):
1926+
SPAM = 42
1927+
1928+
def __init__(self, *args, **kwargs):
1929+
super().__init__(*args, **kwargs)
1930+
1931+
self.assertEqual(Meta.__definition_order__,
1932+
('__module__', '__qualname__',
1933+
'SPAM', '__init__'))
1934+
1935+
def test_type_definition_order_OrderedDict(self):
1936+
class Meta(type):
1937+
def __prepare__(self, *args, **kwargs):
1938+
return OrderedDict()
1939+
1940+
class WithODict(metaclass=Meta):
1941+
x='y'
1942+
1943+
self.assertEqual(WithODict.__definition_order__,
1944+
('__module__', '__qualname__', 'x'))
1945+
1946+
class Meta(type):
1947+
def __prepare__(self, *args, **kwargs):
1948+
class ODictSub(OrderedDict):
1949+
pass
1950+
return ODictSub()
1951+
1952+
class WithODictSub(metaclass=Meta):
1953+
x='y'
1954+
1955+
self.assertEqual(WithODictSub.__definition_order__,
1956+
('__module__', '__qualname__', 'x'))
1957+
1958+
@cpython_only
1959+
def test_type_definition_order_cpython(self):
1960+
# some implementations will have an ordered-by-default dict.
1961+
1962+
class Meta(type):
1963+
def __prepare__(self, *args, **kwargs):
1964+
return {}
1965+
1966+
class NotOrdered(metaclass=Meta):
1967+
x='y'
1968+
1969+
self.assertEqual(NotOrdered.__definition_order__, None)
1970+
17811971
def test_bad_args(self):
17821972
with self.assertRaises(TypeError):
17831973
type()

Lib/test/test_metaclass.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
meta: C ()
181181
ns: [('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
182182
kw: []
183-
>>> type(C) is dict
183+
>>> type(C) is types._DefaultClassNamespaceType
184184
True
185185
>>> print(sorted(C.items()))
186186
[('__module__', 'test.test_metaclass'), ('__qualname__', 'C'), ('a', 42), ('b', 24)]
@@ -211,8 +211,11 @@
211211
212212
The default metaclass must define a __prepare__() method.
213213
214-
>>> type.__prepare__()
215-
{}
214+
>>> ns = type.__prepare__()
215+
>>> type(ns) is types._DefaultClassNamespaceType
216+
True
217+
>>> list(ns) == []
218+
True
216219
>>>
217220
218221
Make sure it works with subclassing.
@@ -248,7 +251,9 @@
248251
249252
"""
250253

254+
from collections import OrderedDict
251255
import sys
256+
import types
252257

253258
# Trace function introduces __locals__ which causes various tests to fail.
254259
if hasattr(sys, 'gettrace') and sys.gettrace():

Lib/test/test_pydoc.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ def test_html_doc(self):
427427
expected_html = expected_html_pattern % (
428428
(mod_url, mod_file, doc_loc) +
429429
expected_html_data_docstrings)
430+
self.maxDiff = None
430431
self.assertEqual(result, expected_html)
431432

432433
@unittest.skipIf(sys.flags.optimize >= 2,
@@ -473,13 +474,18 @@ def test_getpager_with_stdin_none(self):
473474
def test_non_str_name(self):
474475
# issue14638
475476
# Treat illegal (non-str) name like no name
477+
# Definition order is set to None so it looks the same in both
478+
# cases.
476479
class A:
480+
__definition_order__ = None
477481
__name__ = 42
478482
class B:
479483
pass
480484
adoc = pydoc.render_doc(A())
481485
bdoc = pydoc.render_doc(B())
482-
self.assertEqual(adoc.replace("A", "B"), bdoc)
486+
self.maxDiff = None
487+
expected = adoc.replace("A", "B")
488+
self.assertEqual(bdoc, expected)
483489

484490
def test_not_here(self):
485491
missing_module = "test.i_am_not_here"

0 commit comments

Comments
 (0)