(original) (raw)
`. 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);