Python-checkins
Threads by month
- ----- 2024 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
April 2021
- 1 participants
- 580 discussions
https://github.com/python/cpython/commit/0cad068ec174bbe33fb80460da56eb413f…
commit: 0cad068ec174bbe33fb80460da56eb413f3b9359
branch: master
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2021-04-30T14:06:49+02:00
summary:
bpo-43916: Remove _disabled_new() function (GH-25745)
posix and _hashlib use the new Py_TPFLAGS_DISALLOW_INSTANTIATION
flag on their heap types, rather than using a custom tp_new function
(_disabled_new).
files:
M Lib/test/test_hashlib.py
M Lib/test/test_hmac.py
M Modules/_hashopenssl.c
M Modules/posixmodule.c
diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py
index 9e9c874445c27..1236aa723b199 100644
--- a/Lib/test/test_hashlib.py
+++ b/Lib/test/test_hashlib.py
@@ -905,11 +905,11 @@ def test_get_fips_mode(self):
def test_internal_types(self):
# internal types like _hashlib.HASH are not constructable
with self.assertRaisesRegex(
- TypeError, "cannot create 'HASH' instance"
+ TypeError, "cannot create '_hashlib.HASH' instance"
):
HASH()
with self.assertRaisesRegex(
- TypeError, "cannot create 'HASHXOF' instance"
+ TypeError, "cannot create '_hashlib.HASHXOF' instance"
):
HASHXOF()
diff --git a/Lib/test/test_hmac.py b/Lib/test/test_hmac.py
index adf52adbf22af..22d74e9a1138a 100644
--- a/Lib/test/test_hmac.py
+++ b/Lib/test/test_hmac.py
@@ -440,7 +440,7 @@ def test_withmodule(self):
def test_internal_types(self):
# internal types like _hashlib.C_HMAC are not constructable
with self.assertRaisesRegex(
- TypeError, "cannot create 'HMAC' instance"
+ TypeError, "cannot create '_hashlib.HMAC' instance"
):
C_HMAC()
diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c
index 870ee89fdafc6..de9bdd4f3062d 100644
--- a/Modules/_hashopenssl.c
+++ b/Modules/_hashopenssl.c
@@ -118,15 +118,6 @@ _setException(PyObject *exc)
}
/* LCOV_EXCL_STOP */
-/* {Py_tp_new, NULL} doesn't block __new__ */
-static PyObject *
-_disabled_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
-{
- PyErr_Format(PyExc_TypeError,
- "cannot create '%.100s' instances", _PyType_Name(type));
- return NULL;
-}
-
static PyObject*
py_digest_name(const EVP_MD *md)
{
@@ -590,7 +581,6 @@ static PyType_Slot EVPtype_slots[] = {
{Py_tp_doc, (char *)hashtype_doc},
{Py_tp_methods, EVP_methods},
{Py_tp_getset, EVP_getseters},
- {Py_tp_new, _disabled_new},
{0, 0},
};
@@ -598,7 +588,7 @@ static PyType_Spec EVPtype_spec = {
"_hashlib.HASH", /*tp_name*/
sizeof(EVPobject), /*tp_basicsize*/
0, /*tp_itemsize*/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION,
EVPtype_slots
};
@@ -740,7 +730,6 @@ static PyType_Slot EVPXOFtype_slots[] = {
{Py_tp_doc, (char *)hashxoftype_doc},
{Py_tp_methods, EVPXOF_methods},
{Py_tp_getset, EVPXOF_getseters},
- {Py_tp_new, _disabled_new},
{0, 0},
};
@@ -748,7 +737,7 @@ static PyType_Spec EVPXOFtype_spec = {
"_hashlib.HASHXOF", /*tp_name*/
sizeof(EVPobject), /*tp_basicsize*/
0, /*tp_itemsize*/
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DISALLOW_INSTANTIATION,
EVPXOFtype_slots
};
@@ -1734,14 +1723,13 @@ static PyType_Slot HMACtype_slots[] = {
{Py_tp_dealloc,(destructor)_hmac_dealloc},
{Py_tp_methods, HMAC_methods},
{Py_tp_getset, HMAC_getset},
- {Py_tp_new, _disabled_new},
{0, NULL}
};
PyType_Spec HMACtype_spec = {
"_hashlib.HMAC", /* name */
sizeof(HMACobject), /* basicsize */
- .flags = Py_TPFLAGS_DEFAULT,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.slots = HMACtype_slots,
};
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index ecd210e4babf5..25ddc82cb893a 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -13415,14 +13415,6 @@ typedef struct {
#endif
} DirEntry;
-static PyObject *
-_disabled_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
-{
- PyErr_Format(PyExc_TypeError,
- "cannot create '%.100s' instances", _PyType_Name(type));
- return NULL;
-}
-
static void
DirEntry_dealloc(DirEntry *entry)
{
@@ -13781,7 +13773,6 @@ static PyMethodDef DirEntry_methods[] = {
};
static PyType_Slot DirEntryType_slots[] = {
- {Py_tp_new, _disabled_new},
{Py_tp_dealloc, DirEntry_dealloc},
{Py_tp_repr, DirEntry_repr},
{Py_tp_methods, DirEntry_methods},
@@ -13793,7 +13784,7 @@ static PyType_Spec DirEntryType_spec = {
MODNAME ".DirEntry",
sizeof(DirEntry),
0,
- Py_TPFLAGS_DEFAULT,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
DirEntryType_slots
};
@@ -14213,7 +14204,6 @@ static PyMethodDef ScandirIterator_methods[] = {
};
static PyType_Slot ScandirIteratorType_slots[] = {
- {Py_tp_new, _disabled_new},
{Py_tp_dealloc, ScandirIterator_dealloc},
{Py_tp_finalize, ScandirIterator_finalize},
{Py_tp_iter, PyObject_SelfIter},
@@ -14228,7 +14218,8 @@ static PyType_Spec ScandirIteratorType_spec = {
0,
// bpo-40549: Py_TPFLAGS_BASETYPE should not be used, since
// PyType_GetModule(Py_TYPE(self)) doesn't work on a subclass instance.
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_FINALIZE,
+ (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_FINALIZE
+ | Py_TPFLAGS_DISALLOW_INSTANTIATION),
ScandirIteratorType_slots
};
1
0
30 Apr '21
https://github.com/python/cpython/commit/3bb09947ec4837de75532e21dd4bd25db0…
commit: 3bb09947ec4837de75532e21dd4bd25db0a1f1b7
branch: master
author: Victor Stinner <vstinner(a)python.org>
committer: vstinner <vstinner(a)python.org>
date: 2021-04-30T12:46:15+02:00
summary:
bpo-43916: Add Py_TPFLAGS_DISALLOW_INSTANTIATION type flag (GH-25721)
Add a new Py_TPFLAGS_DISALLOW_INSTANTIATION type flag to disallow
creating type instances: set tp_new to NULL and don't create the
"__new__" key in the type dictionary.
The flag is set automatically on static types if tp_base is NULL or
&PyBaseObject_Type and tp_new is NULL.
Use the flag on the following types:
* _curses.ncurses_version type
* _curses_panel.panel
* _tkinter.Tcl_Obj
* _tkinter.tkapp
* _tkinter.tktimertoken
* _xxsubinterpretersmodule.ChannelID
* sys.flags type
* sys.getwindowsversion() type
* sys.version_info type
Update MyStr example in the C API documentation to use
Py_TPFLAGS_DISALLOW_INSTANTIATION.
Add _PyStructSequence_InitType() function to create a structseq type
with the Py_TPFLAGS_DISALLOW_INSTANTIATION flag set.
type_new() calls _PyType_CheckConsistency() at exit.
files:
A Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst
M Doc/c-api/typeobj.rst
M Doc/whatsnew/3.10.rst
M Include/object.h
M Include/structseq.h
M Lib/test/test_sys.py
M Modules/_curses_panel.c
M Modules/_cursesmodule.c
M Modules/_tkinter.c
M Modules/_xxsubinterpretersmodule.c
M Objects/structseq.c
M Objects/typeobject.c
M Python/sysmodule.c
diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst
index 85f0262d10138..5e731bd5a6cbd 100644
--- a/Doc/c-api/typeobj.rst
+++ b/Doc/c-api/typeobj.rst
@@ -1199,6 +1199,25 @@ and :c:type:`PyType_Type` effectively act as defaults.)
.. versionadded:: 3.10
+ .. data:: Py_TPFLAGS_DISALLOW_INSTANTIATION
+
+ Disallow creating instances of the type: set
+ :c:member:`~PyTypeObject.tp_new` to NULL and don't create the ``__new__``
+ key in the type dictionary.
+
+ The flag must be set before creating the type, not after. For example, it
+ must be set before :c:func:`PyType_Ready` is called on the type.
+
+ The flag is set automatically on :ref:`static types <static-types>` if
+ :c:member:`~PyTypeObject.tp_base` is NULL or ``&PyBaseObject_Type`` and
+ :c:member:`~PyTypeObject.tp_new` is NULL.
+
+ **Inheritance:**
+
+ This flag is not inherited.
+
+ .. versionadded:: 3.10
+
.. c:member:: const char* PyTypeObject.tp_doc
@@ -1761,6 +1780,9 @@ and :c:type:`PyType_Type` effectively act as defaults.)
in :c:member:`~PyTypeObject.tp_new`, while for mutable types, most initialization should be
deferred to :c:member:`~PyTypeObject.tp_init`.
+ Set the :const:`Py_TPFLAGS_DISALLOW_INSTANTIATION` flag to disallow creating
+ instances of the type in Python.
+
**Inheritance:**
This field is inherited by subtypes, except it is not inherited by
@@ -2596,7 +2618,8 @@ A type that supports weakrefs, instance dicts, and hashing::
};
A str subclass that cannot be subclassed and cannot be called
-to create instances (e.g. uses a separate factory func)::
+to create instances (e.g. uses a separate factory func) using
+:c:data:`Py_TPFLAGS_DISALLOW_INSTANTIATION` flag::
typedef struct {
PyUnicodeObject raw;
@@ -2609,8 +2632,7 @@ to create instances (e.g. uses a separate factory func)::
.tp_basicsize = sizeof(MyStr),
.tp_base = NULL, // set to &PyUnicode_Type in module init
.tp_doc = "my custom str",
- .tp_flags = Py_TPFLAGS_DEFAULT,
- .tp_new = NULL,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.tp_repr = (reprfunc)myobj_repr,
};
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index 2580a0368d0b3..1a390663c8a40 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -1708,6 +1708,10 @@ New Features
These functions allow to activate, deactivate and query the state of the garbage collector from C code without
having to import the :mod:`gc` module.
+* Add a new :c:data:`Py_TPFLAGS_DISALLOW_INSTANTIATION` type flag to disallow
+ creating type instances.
+ (Contributed by Victor Stinner in :issue:`43916`.)
+
Porting to Python 3.10
----------------------
diff --git a/Include/object.h b/Include/object.h
index cffe72cda869f..4c069998574b4 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -327,6 +327,10 @@ given type object has a specified feature.
#define Py_TPFLAGS_MAPPING (1 << 6)
#endif
+/* Disallow creating instances of the type: set tp_new to NULL and don't create
+ * the "__new__" key in the type dictionary. */
+#define Py_TPFLAGS_DISALLOW_INSTANTIATION (1UL << 7)
+
/* Set if the type object is immutable: type attributes cannot be set nor deleted */
#define Py_TPFLAGS_IMMUTABLETYPE (1UL << 8)
diff --git a/Include/structseq.h b/Include/structseq.h
index 8f51c89163a4e..af3af415b013a 100644
--- a/Include/structseq.h
+++ b/Include/structseq.h
@@ -27,6 +27,12 @@ PyAPI_FUNC(void) PyStructSequence_InitType(PyTypeObject *type,
PyAPI_FUNC(int) PyStructSequence_InitType2(PyTypeObject *type,
PyStructSequence_Desc *desc);
#endif
+#ifdef Py_BUILD_CORE
+extern int _PyStructSequence_InitType(
+ PyTypeObject *type,
+ PyStructSequence_Desc *desc,
+ unsigned long tp_flags);
+#endif
PyAPI_FUNC(PyTypeObject*) PyStructSequence_NewType(PyStructSequence_Desc *desc);
PyAPI_FUNC(PyObject *) PyStructSequence_New(PyTypeObject* type);
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index ee39375af3160..1fd5247a91bb5 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -605,11 +605,12 @@ def test_sys_flags(self):
def assert_raise_on_new_sys_type(self, sys_attr):
# Users are intentionally prevented from creating new instances of
# sys.flags, sys.version_info, and sys.getwindowsversion.
+ arg = sys_attr
attr_type = type(sys_attr)
with self.assertRaises(TypeError):
- attr_type()
+ attr_type(arg)
with self.assertRaises(TypeError):
- attr_type.__new__(attr_type)
+ attr_type.__new__(attr_type, arg)
def test_sys_flags_no_instantiation(self):
self.assert_raise_on_new_sys_type(sys.flags)
diff --git a/Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst b/Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst
new file mode 100644
index 0000000000000..0cec8103645e3
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2021-04-29-17-35-48.bpo-43916.wvWt23.rst
@@ -0,0 +1,2 @@
+Add a new :c:data:`Py_TPFLAGS_DISALLOW_INSTANTIATION` type flag to disallow
+creating type instances. Patch by Victor Stinner.
diff --git a/Modules/_curses_panel.c b/Modules/_curses_panel.c
index 94caf8c93bc8c..0b328f9665a0a 100644
--- a/Modules/_curses_panel.c
+++ b/Modules/_curses_panel.c
@@ -520,7 +520,7 @@ static PyType_Slot PyCursesPanel_Type_slots[] = {
static PyType_Spec PyCursesPanel_Type_spec = {
.name = "_curses_panel.panel",
.basicsize = sizeof(PyCursesPanelObject),
- .flags = Py_TPFLAGS_DEFAULT,
+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
.slots = PyCursesPanel_Type_slots
};
@@ -656,7 +656,6 @@ _curses_panel_exec(PyObject *mod)
if (state->PyCursesPanel_Type == NULL) {
return -1;
}
- ((PyTypeObject *)state->PyCursesPanel_Type)->tp_new = NULL;
if (PyModule_AddType(mod, state->PyCursesPanel_Type) < 0) {
return -1;
diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c
index d221cf1a92520..bcf9ad19386b2 100644
--- a/Modules/_cursesmodule.c
+++ b/Modules/_cursesmodule.c
@@ -4793,9 +4793,11 @@ PyInit__curses(void)
#ifdef NCURSES_VERSION
/* ncurses_version */
if (NcursesVersionType.tp_name == NULL) {
- if (PyStructSequence_InitType2(&NcursesVersionType,
- &ncurses_version_desc) < 0)
+ if (_PyStructSequence_InitType(&NcursesVersionType,
+ &ncurses_version_desc,
+ Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
return NULL;
+ }
}
v = make_ncurses_version();
if (v == NULL) {
@@ -4803,15 +4805,6 @@ PyInit__curses(void)
}
PyDict_SetItemString(d, "ncurses_version", v);
Py_DECREF(v);
-
- /* prevent user from creating new instances */
- NcursesVersionType.tp_init = NULL;
- NcursesVersionType.tp_new = NULL;
- if (PyDict_DelItemString(NcursesVersionType.tp_dict, "__new__") < 0 &&
- PyErr_ExceptionMatches(PyExc_KeyError))
- {
- PyErr_Clear();
- }
#endif /* NCURSES_VERSION */
SetDictInt("ERR", ERR);
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 46d6a6e0954f5..3a0e5def0cf3c 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -1002,7 +1002,7 @@ static PyType_Spec PyTclObject_Type_spec = {
"_tkinter.Tcl_Obj",
sizeof(PyTclObject),
0,
- Py_TPFLAGS_DEFAULT,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
PyTclObject_Type_slots,
};
@@ -3294,7 +3294,7 @@ static PyType_Spec Tktt_Type_spec = {
"_tkinter.tktimertoken",
sizeof(TkttObject),
0,
- Py_TPFLAGS_DEFAULT,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
Tktt_Type_slots,
};
@@ -3349,7 +3349,7 @@ static PyType_Spec Tkapp_Type_spec = {
"_tkinter.tkapp",
sizeof(TkappObject),
0,
- Py_TPFLAGS_DEFAULT,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
Tkapp_Type_slots,
};
@@ -3537,7 +3537,6 @@ PyInit__tkinter(void)
Py_DECREF(m);
return NULL;
}
- ((PyTypeObject *)o)->tp_new = NULL;
if (PyModule_AddObject(m, "TkappType", o)) {
Py_DECREF(o);
Py_DECREF(m);
@@ -3550,7 +3549,6 @@ PyInit__tkinter(void)
Py_DECREF(m);
return NULL;
}
- ((PyTypeObject *)o)->tp_new = NULL;
if (PyModule_AddObject(m, "TkttType", o)) {
Py_DECREF(o);
Py_DECREF(m);
@@ -3563,7 +3561,6 @@ PyInit__tkinter(void)
Py_DECREF(m);
return NULL;
}
- ((PyTypeObject *)o)->tp_new = NULL;
if (PyModule_AddObject(m, "Tcl_Obj", o)) {
Py_DECREF(o);
Py_DECREF(m);
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index b94b130d70df7..9290255c66c3f 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -1780,7 +1780,12 @@ static PyTypeObject ChannelIDtype = {
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ // Use Py_TPFLAGS_DISALLOW_INSTANTIATION so the type cannot be instantiated
+ // from Python code. We do this because there is a strong relationship
+ // between channel IDs and the channel lifecycle, so this limitation avoids
+ // related complications. Use the _channel_id() function instead.
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE
+ | Py_TPFLAGS_DISALLOW_INSTANTIATION, /* tp_flags */
channelid_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
@@ -1791,19 +1796,6 @@ static PyTypeObject ChannelIDtype = {
0, /* tp_methods */
0, /* tp_members */
channelid_getsets, /* tp_getset */
- 0, /* tp_base */
- 0, /* tp_dict */
- 0, /* tp_descr_get */
- 0, /* tp_descr_set */
- 0, /* tp_dictoffset */
- 0, /* tp_init */
- 0, /* tp_alloc */
- // Note that we do not set tp_new to channelid_new. Instead we
- // set it to NULL, meaning it cannot be instantiated from Python
- // code. We do this because there is a strong relationship between
- // channel IDs and the channel lifecycle, so this limitation avoids
- // related complications.
- NULL, /* tp_new */
};
diff --git a/Objects/structseq.c b/Objects/structseq.c
index 88e63b658a420..bf59f47fcf073 100644
--- a/Objects/structseq.c
+++ b/Objects/structseq.c
@@ -459,8 +459,10 @@ initialize_members(PyStructSequence_Desc *desc, PyMemberDef* members,
members[k].name = NULL;
}
+
int
-PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
+_PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc,
+ unsigned long tp_flags)
{
PyMemberDef *members;
Py_ssize_t n_members, n_unnamed_members;
@@ -488,7 +490,7 @@ PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
type->tp_base = &PyTuple_Type;
type->tp_methods = structseq_methods;
type->tp_new = structseq_new;
- type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC;
+ type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | tp_flags;
type->tp_traverse = (traverseproc) structseq_traverse;
n_members = count_members(desc, &n_unnamed_members);
@@ -516,6 +518,12 @@ PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
return 0;
}
+int
+PyStructSequence_InitType2(PyTypeObject *type, PyStructSequence_Desc *desc)
+{
+ return _PyStructSequence_InitType(type, desc, 0);
+}
+
void
PyStructSequence_InitType(PyTypeObject *type, PyStructSequence_Desc *desc)
{
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 1f8e2572a2daf..0f7f280bca0d6 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -158,6 +158,11 @@ _PyType_CheckConsistency(PyTypeObject *type)
CHECK(!(type->tp_flags & Py_TPFLAGS_READYING));
CHECK(type->tp_dict != NULL);
+ if (type->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION) {
+ CHECK(type->tp_new == NULL);
+ CHECK(_PyDict_ContainsId(type->tp_dict, &PyId___new__) == 0);
+ }
+
return 1;
#undef CHECK
}
@@ -1111,8 +1116,7 @@ type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
if (type->tp_new == NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
- "cannot create '%.100s' instances",
- type->tp_name);
+ "cannot create '%s' instances", type->tp_name);
return NULL;
}
@@ -3185,6 +3189,8 @@ type_new_impl(type_new_ctx *ctx)
if (type_new_init_subclass(type, ctx->kwds) < 0) {
goto error;
}
+
+ assert(_PyType_CheckConsistency(type));
return (PyObject *)type;
error:
@@ -5651,7 +5657,6 @@ type_add_getset(PyTypeObject *type)
static void
inherit_special(PyTypeObject *type, PyTypeObject *base)
{
-
/* Copying tp_traverse and tp_clear is connected to the GC flags */
if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC) &&
(base->tp_flags & Py_TPFLAGS_HAVE_GC) &&
@@ -5662,23 +5667,7 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
if (type->tp_clear == NULL)
type->tp_clear = base->tp_clear;
}
- {
- /* The condition below could use some explanation.
- It appears that tp_new is not inherited for static types
- whose base class is 'object'; this seems to be a precaution
- so that old extension types don't suddenly become
- callable (object.__new__ wouldn't insure the invariants
- that the extension type's own factory function ensures).
- Heap types, of course, are under our control, so they do
- inherit tp_new; static extension types that specify some
- other built-in type as the default also
- inherit object.__new__. */
- if (base != &PyBaseObject_Type ||
- (type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
- if (type->tp_new == NULL)
- type->tp_new = base->tp_new;
- }
- }
+
if (type->tp_basicsize == 0)
type->tp_basicsize = base->tp_basicsize;
@@ -5941,6 +5930,7 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
}
static int add_operators(PyTypeObject *);
+static int add_tp_new_wrapper(PyTypeObject *type);
static int
@@ -5991,6 +5981,7 @@ type_ready_set_bases(PyTypeObject *type)
type->tp_base = base;
}
}
+ assert(type->tp_base != NULL || type == &PyBaseObject_Type);
/* Now the only way base can still be NULL is if type is
* &PyBaseObject_Type. */
@@ -6249,6 +6240,50 @@ type_ready_add_subclasses(PyTypeObject *type)
}
+// Set tp_new and the "__new__" key in the type dictionary.
+// Use the Py_TPFLAGS_DISALLOW_INSTANTIATION flag.
+static int
+type_ready_set_new(PyTypeObject *type)
+{
+ PyTypeObject *base = type->tp_base;
+ /* The condition below could use some explanation.
+
+ It appears that tp_new is not inherited for static types whose base
+ class is 'object'; this seems to be a precaution so that old extension
+ types don't suddenly become callable (object.__new__ wouldn't insure the
+ invariants that the extension type's own factory function ensures).
+
+ Heap types, of course, are under our control, so they do inherit tp_new;
+ static extension types that specify some other built-in type as the
+ default also inherit object.__new__. */
+ if (type->tp_new == NULL
+ && base == &PyBaseObject_Type
+ && !(type->tp_flags & Py_TPFLAGS_HEAPTYPE))
+ {
+ type->tp_flags |= Py_TPFLAGS_DISALLOW_INSTANTIATION;
+ }
+
+ if (!(type->tp_flags & Py_TPFLAGS_DISALLOW_INSTANTIATION)) {
+ if (type->tp_new != NULL) {
+ // If "__new__" key does not exists in the type dictionary,
+ // set it to tp_new_wrapper().
+ if (add_tp_new_wrapper(type) < 0) {
+ return -1;
+ }
+ }
+ else {
+ // tp_new is NULL: inherit tp_new from base
+ type->tp_new = base->tp_new;
+ }
+ }
+ else {
+ // Py_TPFLAGS_DISALLOW_INSTANTIATION sets tp_new to NULL
+ type->tp_new = NULL;
+ }
+ return 0;
+}
+
+
static int
type_ready(PyTypeObject *type)
{
@@ -6275,6 +6310,9 @@ type_ready(PyTypeObject *type)
if (type_ready_mro(type) < 0) {
return -1;
}
+ if (type_ready_set_new(type) < 0) {
+ return -1;
+ }
if (type_ready_fill_dict(type) < 0) {
return -1;
}
@@ -6898,8 +6936,8 @@ tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds)
"__new__() called with non-type 'self'");
return NULL;
}
-
type = (PyTypeObject *)self;
+
if (!PyTuple_Check(args) || PyTuple_GET_SIZE(args) < 1) {
PyErr_Format(PyExc_TypeError,
"%s.__new__(): not enough arguments",
@@ -6961,16 +6999,18 @@ static struct PyMethodDef tp_new_methoddef[] = {
static int
add_tp_new_wrapper(PyTypeObject *type)
{
- PyObject *func;
-
int r = _PyDict_ContainsId(type->tp_dict, &PyId___new__);
- if (r > 0)
+ if (r > 0) {
return 0;
- if (r < 0)
+ }
+ if (r < 0) {
return -1;
- func = PyCFunction_NewEx(tp_new_methoddef, (PyObject *)type, NULL);
- if (func == NULL)
+ }
+
+ PyObject *func = PyCFunction_NewEx(tp_new_methoddef, (PyObject *)type, NULL);
+ if (func == NULL) {
return -1;
+ }
r = _PyDict_SetItemId(type->tp_dict, &PyId___new__, func);
Py_DECREF(func);
return r;
@@ -8558,11 +8598,6 @@ add_operators(PyTypeObject *type)
Py_DECREF(descr);
}
}
- if (type->tp_new != NULL) {
- if (add_tp_new_wrapper(type) < 0) {
- return -1;
- }
- }
return 0;
}
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 911c2d967b010..36297ff82e1c3 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -2810,56 +2810,35 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
/* version_info */
if (VersionInfoType.tp_name == NULL) {
- if (PyStructSequence_InitType2(&VersionInfoType,
- &version_info_desc) < 0) {
+ if (_PyStructSequence_InitType(&VersionInfoType,
+ &version_info_desc,
+ Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
goto type_init_failed;
}
}
version_info = make_version_info(tstate);
SET_SYS("version_info", version_info);
- /* prevent user from creating new instances */
- VersionInfoType.tp_init = NULL;
- VersionInfoType.tp_new = NULL;
- res = PyDict_DelItemString(VersionInfoType.tp_dict, "__new__");
- if (res < 0 && _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
- _PyErr_Clear(tstate);
- }
/* implementation */
SET_SYS("implementation", make_impl_info(version_info));
// sys.flags: updated in-place later by _PySys_UpdateConfig()
if (FlagsType.tp_name == 0) {
- if (PyStructSequence_InitType2(&FlagsType, &flags_desc) < 0) {
+ if (_PyStructSequence_InitType(&FlagsType, &flags_desc,
+ Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
goto type_init_failed;
}
}
SET_SYS("flags", make_flags(tstate->interp));
- /* prevent user from creating new instances */
- FlagsType.tp_init = NULL;
- FlagsType.tp_new = NULL;
- res = PyDict_DelItemString(FlagsType.tp_dict, "__new__");
- if (res < 0) {
- if (!_PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
- goto err_occurred;
- }
- _PyErr_Clear(tstate);
- }
#if defined(MS_WINDOWS)
/* getwindowsversion */
- if (WindowsVersionType.tp_name == 0)
- if (PyStructSequence_InitType2(&WindowsVersionType,
- &windows_version_desc) < 0) {
+ if (WindowsVersionType.tp_name == 0) {
+ if (_PyStructSequence_InitType(&WindowsVersionType,
+ &windows_version_desc,
+ Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
goto type_init_failed;
}
- /* prevent user from creating new instances */
- WindowsVersionType.tp_init = NULL;
- WindowsVersionType.tp_new = NULL;
- assert(!_PyErr_Occurred(tstate));
- res = PyDict_DelItemString(WindowsVersionType.tp_dict, "__new__");
- if (res < 0 && _PyErr_ExceptionMatches(tstate, PyExc_KeyError)) {
- _PyErr_Clear(tstate);
}
#endif
1
0
30 Apr '21
https://github.com/python/cpython/commit/b73b5fb9ea08156991a065c1696e8d8cf7…
commit: b73b5fb9ea08156991a065c1696e8d8cf7622482
branch: master
author: Erlend Egeberg Aasland <erlend.aasland(a)innova.no>
committer: vstinner <vstinner(a)python.org>
date: 2021-04-30T12:07:02+02:00
summary:
bpo-43973: object_set_class() checks Py_TPFLAGS_IMMUTABLETYPE (GH-25714)
Use Py_TPFLAGS_IMMUTABLETYPE to check for class assignments.
files:
M Objects/typeobject.c
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 19d619fada0e9..1f8e2572a2daf 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -4737,10 +4737,10 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
*/
if (!(PyType_IsSubtype(newto, &PyModule_Type) &&
PyType_IsSubtype(oldto, &PyModule_Type)) &&
- (!(newto->tp_flags & Py_TPFLAGS_HEAPTYPE) ||
- !(oldto->tp_flags & Py_TPFLAGS_HEAPTYPE))) {
+ (_PyType_HasFeature(newto, Py_TPFLAGS_IMMUTABLETYPE) ||
+ _PyType_HasFeature(oldto, Py_TPFLAGS_IMMUTABLETYPE))) {
PyErr_Format(PyExc_TypeError,
- "__class__ assignment only supported for heap types "
+ "__class__ assignment only supported for mutable types "
"or ModuleType subclasses");
return -1;
}
1
0
https://github.com/python/cpython/commit/069e81ab3da46c441335ca762c4333b7bd…
commit: 069e81ab3da46c441335ca762c4333b7bd91861d
branch: master
author: Mark Shannon <mark(a)hotpy.org>
committer: markshannon <mark(a)hotpy.org>
date: 2021-04-30T09:50:28+01:00
summary:
bpo-43977: Use tp_flags for collection matching (GH-25723)
* Add Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING, add to all relevant standard builtin classes.
* Set relevant flags on collections.abc.Sequence and Mapping.
* Use flags in MATCH_SEQUENCE and MATCH_MAPPING opcodes.
* Inherit Py_TPFLAGS_SEQUENCE and Py_TPFLAGS_MAPPING.
* Add NEWS
* Remove interpreter-state map_abc and seq_abc fields.
files:
A Misc/NEWS.d/next/Core and Builtins/2021-04-29-17-40-25.bpo-43977.FrQhge.rst
M Include/internal/pycore_interp.h
M Include/object.h
M Lib/_collections_abc.py
M Modules/_abc.c
M Modules/_collectionsmodule.c
M Modules/arraymodule.c
M Objects/descrobject.c
M Objects/dictobject.c
M Objects/listobject.c
M Objects/memoryobject.c
M Objects/rangeobject.c
M Objects/tupleobject.c
M Objects/typeobject.c
M Python/ceval.c
M Python/pystate.c
diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h
index 11d31da382958..bfd082b588256 100644
--- a/Include/internal/pycore_interp.h
+++ b/Include/internal/pycore_interp.h
@@ -247,10 +247,6 @@ struct _is {
// importlib module
PyObject *importlib;
- // Kept handy for pattern matching:
- PyObject *map_abc; // _collections_abc.Mapping
- PyObject *seq_abc; // _collections_abc.Sequence
-
/* Used in Modules/_threadmodule.c. */
long num_threads;
/* Support for runtime thread stack size tuning.
diff --git a/Include/object.h b/Include/object.h
index d8476f9213760..cffe72cda869f 100644
--- a/Include/object.h
+++ b/Include/object.h
@@ -320,6 +320,13 @@ Code can use PyType_HasFeature(type_ob, flag_value) to test whether the
given type object has a specified feature.
*/
+#ifndef Py_LIMITED_API
+/* Set if instances of the type object are treated as sequences for pattern matching */
+#define Py_TPFLAGS_SEQUENCE (1 << 5)
+/* Set if instances of the type object are treated as mappings for pattern matching */
+#define Py_TPFLAGS_MAPPING (1 << 6)
+#endif
+
/* Set if the type object is immutable: type attributes cannot be set nor deleted */
#define Py_TPFLAGS_IMMUTABLETYPE (1UL << 8)
diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py
index dddf8a23ff985..d92b848e9436f 100644
--- a/Lib/_collections_abc.py
+++ b/Lib/_collections_abc.py
@@ -793,7 +793,6 @@ def __isub__(self, it):
### MAPPINGS ###
-
class Mapping(Collection):
"""A Mapping is a generic container for associating key/value
pairs.
@@ -804,6 +803,9 @@ class Mapping(Collection):
__slots__ = ()
+ # Tell ABCMeta.__new__ that this class should have TPFLAGS_MAPPING set.
+ __abc_tpflags__ = 1 << 6 # Py_TPFLAGS_MAPPING
+
@abstractmethod
def __getitem__(self, key):
raise KeyError
@@ -842,7 +844,6 @@ def __eq__(self, other):
__reversed__ = None
-
Mapping.register(mappingproxy)
@@ -1011,7 +1012,6 @@ def setdefault(self, key, default=None):
### SEQUENCES ###
-
class Sequence(Reversible, Collection):
"""All the operations on a read-only sequence.
@@ -1021,6 +1021,9 @@ class Sequence(Reversible, Collection):
__slots__ = ()
+ # Tell ABCMeta.__new__ that this class should have TPFLAGS_SEQUENCE set.
+ __abc_tpflags__ = 1 << 5 # Py_TPFLAGS_SEQUENCE
+
@abstractmethod
def __getitem__(self, index):
raise IndexError
@@ -1072,7 +1075,6 @@ def count(self, value):
'S.count(value) -> integer -- return number of occurrences of value'
return sum(1 for v in self if v is value or v == value)
-
Sequence.register(tuple)
Sequence.register(str)
Sequence.register(range)
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-04-29-17-40-25.bpo-43977.FrQhge.rst b/Misc/NEWS.d/next/Core and Builtins/2021-04-29-17-40-25.bpo-43977.FrQhge.rst
new file mode 100644
index 0000000000000..038d7390852ba
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-04-29-17-40-25.bpo-43977.FrQhge.rst
@@ -0,0 +1,2 @@
+Use :c:member:`~PyTypeObject.tp_flags` on the class object to determine if the subject is a sequence
+or mapping when pattern matching. Avoids the need to import :mod:`collections.abc` when pattern matching.
diff --git a/Modules/_abc.c b/Modules/_abc.c
index 0ddc2abeee1e0..39261dd3cd579 100644
--- a/Modules/_abc.c
+++ b/Modules/_abc.c
@@ -15,6 +15,7 @@ PyDoc_STRVAR(_abc__doc__,
_Py_IDENTIFIER(__abstractmethods__);
_Py_IDENTIFIER(__class__);
_Py_IDENTIFIER(__dict__);
+_Py_IDENTIFIER(__abc_tpflags__);
_Py_IDENTIFIER(__bases__);
_Py_IDENTIFIER(_abc_impl);
_Py_IDENTIFIER(__subclasscheck__);
@@ -417,6 +418,8 @@ compute_abstract_methods(PyObject *self)
return ret;
}
+#define COLLECTION_FLAGS (Py_TPFLAGS_SEQUENCE | Py_TPFLAGS_MAPPING)
+
/*[clinic input]
_abc._abc_init
@@ -446,6 +449,31 @@ _abc__abc_init(PyObject *module, PyObject *self)
return NULL;
}
Py_DECREF(data);
+ /* If __abc_tpflags__ & COLLECTION_FLAGS is set, then set the corresponding bit(s)
+ * in the new class.
+ * Used by collections.abc.Sequence and collections.abc.Mapping to indicate
+ * their special status w.r.t. pattern matching. */
+ if (PyType_Check(self)) {
+ PyTypeObject *cls = (PyTypeObject *)self;
+ PyObject *flags = _PyDict_GetItemIdWithError(cls->tp_dict, &PyId___abc_tpflags__);
+ if (flags == NULL) {
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ }
+ else {
+ if (PyLong_CheckExact(flags)) {
+ long val = PyLong_AsLong(flags);
+ if (val == -1 && PyErr_Occurred()) {
+ return NULL;
+ }
+ ((PyTypeObject *)self)->tp_flags |= (val & COLLECTION_FLAGS);
+ }
+ if (_PyDict_DelItemId(cls->tp_dict, &PyId___abc_tpflags__) < 0) {
+ return NULL;
+ }
+ }
+ }
Py_RETURN_NONE;
}
@@ -499,6 +527,11 @@ _abc__abc_register_impl(PyObject *module, PyObject *self, PyObject *subclass)
/* Invalidate negative cache */
get_abc_state(module)->abc_invalidation_counter++;
+ if (PyType_Check(subclass) && PyType_Check(self) &&
+ !PyType_HasFeature((PyTypeObject *)subclass, Py_TPFLAGS_IMMUTABLETYPE))
+ {
+ ((PyTypeObject *)subclass)->tp_flags |= (((PyTypeObject *)self)->tp_flags & COLLECTION_FLAGS);
+ }
Py_INCREF(subclass);
return subclass;
}
diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c
index 8b01a7fad01a2..9c8701af904ab 100644
--- a/Modules/_collectionsmodule.c
+++ b/Modules/_collectionsmodule.c
@@ -1662,7 +1662,8 @@ static PyTypeObject deque_type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+ Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_SEQUENCE,
/* tp_flags */
deque_doc, /* tp_doc */
(traverseproc)deque_traverse, /* tp_traverse */
diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c
index 367621fd03b88..d65c1449eb38e 100644
--- a/Modules/arraymodule.c
+++ b/Modules/arraymodule.c
@@ -2848,7 +2848,8 @@ static PyType_Spec array_spec = {
.name = "array.array",
.basicsize = sizeof(arrayobject),
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
- Py_TPFLAGS_IMMUTABLETYPE),
+ Py_TPFLAGS_IMMUTABLETYPE |
+ Py_TPFLAGS_SEQUENCE),
.slots = array_slots,
};
diff --git a/Objects/descrobject.c b/Objects/descrobject.c
index dd41620b9a9ee..57a9607d10c31 100644
--- a/Objects/descrobject.c
+++ b/Objects/descrobject.c
@@ -1852,7 +1852,8 @@ PyTypeObject PyDictProxy_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
+ Py_TPFLAGS_MAPPING, /* tp_flags */
0, /* tp_doc */
mappingproxy_traverse, /* tp_traverse */
0, /* tp_clear */
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 0aeee7011e844..9e2c12229df80 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -3553,7 +3553,7 @@ PyTypeObject PyDict_Type = {
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DICT_SUBCLASS |
- _Py_TPFLAGS_MATCH_SELF, /* tp_flags */
+ _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_MAPPING, /* tp_flags */
dictionary_doc, /* tp_doc */
dict_traverse, /* tp_traverse */
dict_tp_clear, /* tp_clear */
diff --git a/Objects/listobject.c b/Objects/listobject.c
index e7987a6d352bf..6eb7dce759cf8 100644
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -3053,7 +3053,7 @@ PyTypeObject PyList_Type = {
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LIST_SUBCLASS |
- _Py_TPFLAGS_MATCH_SELF, /* tp_flags */
+ _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE, /* tp_flags */
list___init____doc__, /* tp_doc */
(traverseproc)list_traverse, /* tp_traverse */
(inquiry)_list_clear, /* tp_clear */
diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index d328f4d40b7a4..913d358062219 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -3287,7 +3287,8 @@ PyTypeObject PyMemoryView_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
&memory_as_buffer, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
+ Py_TPFLAGS_SEQUENCE, /* tp_flags */
memoryview__doc__, /* tp_doc */
(traverseproc)memory_traverse, /* tp_traverse */
(inquiry)memory_clear, /* tp_clear */
diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c
index 530426c8ac904..3e05707b1cee6 100644
--- a/Objects/rangeobject.c
+++ b/Objects/rangeobject.c
@@ -735,7 +735,7 @@ PyTypeObject PyRange_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
- Py_TPFLAGS_DEFAULT, /* tp_flags */
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_SEQUENCE, /* tp_flags */
range_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c
index 89c393ccc5fbc..6b1ab740121e8 100644
--- a/Objects/tupleobject.c
+++ b/Objects/tupleobject.c
@@ -918,7 +918,7 @@ PyTypeObject PyTuple_Type = {
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TUPLE_SUBCLASS |
- _Py_TPFLAGS_MATCH_SELF, /* tp_flags */
+ _Py_TPFLAGS_MATCH_SELF | Py_TPFLAGS_SEQUENCE, /* tp_flags */
tuple_new__doc__, /* tp_doc */
(traverseproc)tupletraverse, /* tp_traverse */
0, /* tp_clear */
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index ac4dc1da4411d..19d619fada0e9 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -5717,10 +5717,15 @@ inherit_special(PyTypeObject *type, PyTypeObject *base)
else if (PyType_IsSubtype(base, &PyDict_Type)) {
type->tp_flags |= Py_TPFLAGS_DICT_SUBCLASS;
}
-
if (PyType_HasFeature(base, _Py_TPFLAGS_MATCH_SELF)) {
type->tp_flags |= _Py_TPFLAGS_MATCH_SELF;
}
+ if (PyType_HasFeature(base, Py_TPFLAGS_SEQUENCE)) {
+ type->tp_flags |= Py_TPFLAGS_SEQUENCE;
+ }
+ if (PyType_HasFeature(base, Py_TPFLAGS_MAPPING)) {
+ type->tp_flags |= Py_TPFLAGS_MAPPING;
+ }
}
static int
diff --git a/Python/ceval.c b/Python/ceval.c
index 326930b706c43..866c57afdbf9d 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -3889,76 +3889,20 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
}
case TARGET(MATCH_MAPPING): {
- // PUSH(isinstance(TOS, _collections_abc.Mapping))
PyObject *subject = TOP();
- // Fast path for dicts:
- if (PyDict_Check(subject)) {
- Py_INCREF(Py_True);
- PUSH(Py_True);
- DISPATCH();
- }
- // Lazily import _collections_abc.Mapping, and keep it handy on the
- // PyInterpreterState struct (it gets cleaned up at exit):
- PyInterpreterState *interp = PyInterpreterState_Get();
- if (interp->map_abc == NULL) {
- PyObject *abc = PyImport_ImportModule("_collections_abc");
- if (abc == NULL) {
- goto error;
- }
- interp->map_abc = PyObject_GetAttrString(abc, "Mapping");
- if (interp->map_abc == NULL) {
- goto error;
- }
- }
- int match = PyObject_IsInstance(subject, interp->map_abc);
- if (match < 0) {
- goto error;
- }
- PUSH(PyBool_FromLong(match));
+ int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_MAPPING;
+ PyObject *res = match ? Py_True : Py_False;
+ Py_INCREF(res);
+ PUSH(res);
DISPATCH();
}
case TARGET(MATCH_SEQUENCE): {
- // PUSH(not isinstance(TOS, (bytearray, bytes, str))
- // and isinstance(TOS, _collections_abc.Sequence))
PyObject *subject = TOP();
- // Fast path for lists and tuples:
- if (PyType_FastSubclass(Py_TYPE(subject),
- Py_TPFLAGS_LIST_SUBCLASS |
- Py_TPFLAGS_TUPLE_SUBCLASS))
- {
- Py_INCREF(Py_True);
- PUSH(Py_True);
- DISPATCH();
- }
- // Bail on some possible Sequences that we intentionally exclude:
- if (PyType_FastSubclass(Py_TYPE(subject),
- Py_TPFLAGS_BYTES_SUBCLASS |
- Py_TPFLAGS_UNICODE_SUBCLASS) ||
- PyByteArray_Check(subject))
- {
- Py_INCREF(Py_False);
- PUSH(Py_False);
- DISPATCH();
- }
- // Lazily import _collections_abc.Sequence, and keep it handy on the
- // PyInterpreterState struct (it gets cleaned up at exit):
- PyInterpreterState *interp = PyInterpreterState_Get();
- if (interp->seq_abc == NULL) {
- PyObject *abc = PyImport_ImportModule("_collections_abc");
- if (abc == NULL) {
- goto error;
- }
- interp->seq_abc = PyObject_GetAttrString(abc, "Sequence");
- if (interp->seq_abc == NULL) {
- goto error;
- }
- }
- int match = PyObject_IsInstance(subject, interp->seq_abc);
- if (match < 0) {
- goto error;
- }
- PUSH(PyBool_FromLong(match));
+ int match = Py_TYPE(subject)->tp_flags & Py_TPFLAGS_SEQUENCE;
+ PyObject *res = match ? Py_True : Py_False;
+ Py_INCREF(res);
+ PUSH(res);
DISPATCH();
}
diff --git a/Python/pystate.c b/Python/pystate.c
index 81bcf68219a0e..aeebd6f61c6d7 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -308,8 +308,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
Py_CLEAR(interp->importlib);
Py_CLEAR(interp->import_func);
Py_CLEAR(interp->dict);
- Py_CLEAR(interp->map_abc);
- Py_CLEAR(interp->seq_abc);
#ifdef HAVE_FORK
Py_CLEAR(interp->before_forkers);
Py_CLEAR(interp->after_forkers_parent);
1
0
30 Apr '21
https://github.com/python/cpython/commit/a92d7387632de1fc64de51f22f6191acd0…
commit: a92d7387632de1fc64de51f22f6191acd0c6f5c0
branch: 3.9
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: miss-islington <31488909+miss-islington(a)users.noreply.github.com>
date: 2021-04-30T00:56:56-07:00
summary:
bpo-43954: Fix a missing word in the unittest docs (GH-25672)
(cherry picked from commit 2abbd8f2add5e80b86a965625b9a77ae94a101cd)
Co-authored-by: Zackery Spytz <zspytz(a)gmail.com>
files:
M Doc/library/unittest.rst
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index ebed5c08082d0..9541997e3ccca 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -331,8 +331,9 @@ the `load_tests protocol`_.
.. versionchanged:: 3.4
Test discovery supports :term:`namespace packages <namespace package>`
- for start directory. Note that you need to the top level directory too.
- (e.g. ``python -m unittest discover -s root/namespace -t root``).
+ for the start directory. Note that you need to specify the top level
+ directory too (e.g.
+ ``python -m unittest discover -s root/namespace -t root``).
.. _organizing-tests:
1
0
https://github.com/python/cpython/commit/2abbd8f2add5e80b86a965625b9a77ae94…
commit: 2abbd8f2add5e80b86a965625b9a77ae94a101cd
branch: master
author: Zackery Spytz <zspytz(a)gmail.com>
committer: JulienPalard <julien(a)palard.fr>
date: 2021-04-30T09:32:19+02:00
summary:
bpo-43954: Fix a missing word in the unittest docs (GH-25672)
files:
M Doc/library/unittest.rst
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index d413224fd4983..0c29408cb1921 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -331,8 +331,9 @@ the `load_tests protocol`_.
.. versionchanged:: 3.4
Test discovery supports :term:`namespace packages <namespace package>`
- for start directory. Note that you need to the top level directory too.
- (e.g. ``python -m unittest discover -s root/namespace -t root``).
+ for the start directory. Note that you need to specify the top level
+ directory too (e.g.
+ ``python -m unittest discover -s root/namespace -t root``).
.. _organizing-tests:
1
0
https://github.com/python/cpython/commit/74613a46fc79cacc88d3eae4105b12691c…
commit: 74613a46fc79cacc88d3eae4105b12691cd4ba20
branch: master
author: larryhastings <larry(a)hastings.org>
committer: larryhastings <larry(a)hastings.org>
date: 2021-04-29T21:16:28-07:00
summary:
bpo-43817: Add inspect.get_annotations(). (#25522)
Add inspect.get_annotations, which safely computes the annotations defined on an object. It works around the quirks of accessing the annotations from various types of objects, and makes very few assumptions about the object passed in. inspect.get_annotations can also correctly un-stringize stringized annotations.
inspect.signature, inspect.from_callable, and inspect.from_function now call inspect.get_annotations to retrieve annotations. This means inspect.signature and inspect.from_callable can now un-stringize stringized annotations, too.
files:
A Lib/test/inspect_stock_annotations.py
A Lib/test/inspect_stringized_annotations.py
A Lib/test/inspect_stringized_annotations_2.py
A Misc/NEWS.d/next/Library/2021-04-22-04-12-13.bpo-43817.FQ-XlH.rst
M Doc/library/inspect.rst
M Lib/inspect.py
M Lib/test/test_inspect.py
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 10339641c3b43..56c2f76708d43 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -562,7 +562,7 @@ The Signature object represents the call signature of a callable object and its
return annotation. To retrieve a Signature object, use the :func:`signature`
function.
-.. function:: signature(callable, *, follow_wrapped=True, globalns=None, localns=None)
+.. function:: signature(callable, *, follow_wrapped=True, globals=None, locals=None, eval_str=False)
Return a :class:`Signature` object for the given ``callable``::
@@ -584,11 +584,20 @@ function.
Accepts a wide range of Python callables, from plain functions and classes to
:func:`functools.partial` objects.
- Raises :exc:`ValueError` if no signature can be provided, and
- :exc:`TypeError` if that type of object is not supported.
+ For objects defined in modules using stringized annotations
+ (``from __future__ import annotations``), :func:`signature` will
+ attempt to automatically un-stringize the annotations using
+ :func:`inspect.get_annotations()`. The
+ ``global``, ``locals``, and ``eval_str`` parameters are passed
+ into :func:`inspect.get_annotations()` when resolving the
+ annotations; see the documentation for :func:`inspect.get_annotations()`
+ for instructions on how to use these parameters.
- ``globalns`` and ``localns`` are passed into
- :func:`typing.get_type_hints` when resolving the annotations.
+ Raises :exc:`ValueError` if no signature can be provided, and
+ :exc:`TypeError` if that type of object is not supported. Also,
+ if the annotations are stringized, and ``eval_str`` is not false,
+ the ``eval()`` call(s) to un-stringize the annotations could
+ potentially raise any kind of exception.
A slash(/) in the signature of a function denotes that the parameters prior
to it are positional-only. For more info, see
@@ -600,7 +609,7 @@ function.
unwrap decorated callables.)
.. versionadded:: 3.10
- ``globalns`` and ``localns`` parameters.
+ ``globals``, ``locals``, and ``eval_str`` parameters.
.. note::
@@ -608,12 +617,6 @@ function.
Python. For example, in CPython, some built-in functions defined in
C provide no metadata about their arguments.
- .. note::
-
- Will first try to resolve the annotations, but when it fails and
- encounters with an error while that operation, the annotations will be
- returned unchanged (as strings).
-
.. class:: Signature(parameters=None, *, return_annotation=Signature.empty)
@@ -1115,6 +1118,55 @@ Classes and functions
.. versionadded:: 3.4
+.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False)
+
+ Compute the annotations dict for an object.
+
+ ``obj`` may be a callable, class, or module.
+ Passing in an object of any other type raises :exc:`TypeError`.
+
+ Returns a dict. ``get_annotations()`` returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This function handles several details for you:
+
+ * If ``eval_str`` is true, values of type ``str`` will
+ be un-stringized using :func:`eval()`. This is intended
+ for use with stringized annotations
+ (``from __future__ import annotations``).
+ * If ``obj`` doesn't have an annotations dict, returns an
+ empty dict. (Functions and methods always have an
+ annotations dict; classes, modules, and other types of
+ callables may not.)
+ * Ignores inherited annotations on classes. If a class
+ doesn't have its own annotations dict, returns an empty dict.
+ * All accesses to object members and dict values are done
+ using ``getattr()`` and ``dict.get()`` for safety.
+ * Always, always, always returns a freshly-created dict.
+
+ ``eval_str`` controls whether or not values of type ``str`` are replaced
+ with the result of calling :func:`eval()` on those values:
+
+ * If eval_str is true, :func:`eval()` is called on values of type ``str``.
+ * If eval_str is false (the default), values of type ``str`` are unchanged.
+
+ ``globals`` and ``locals`` are passed in to :func:`eval()`; see the documentation
+ for :func:`eval()` for more information. If ``globals`` or ``locals``
+ is ``None``, this function may replace that value with a context-specific
+ default, contingent on ``type(obj)``:
+
+ * If ``obj`` is a module, ``globals`` defaults to ``obj.__dict__``.
+ * If ``obj`` is a class, ``globals`` defaults to
+ ``sys.modules[obj.__module__].__dict__`` and ``locals`` defaults
+ to the ``obj`` class namespace.
+ * If ``obj`` is a callable, ``globals`` defaults to ``obj.__globals__``,
+ although if ``obj`` is a wrapped function (using
+ ``functools.update_wrapper()``) it is first unwrapped.
+
+ .. versionadded:: 3.10
+
+
.. _inspect-stack:
The interpreter stack
diff --git a/Lib/inspect.py b/Lib/inspect.py
index b8e247ec252bb..9f8cc01310f10 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -24,6 +24,8 @@
stack(), trace() - get info about frames on the stack or in a traceback
signature() - get a Signature object for the callable
+
+ get_annotations() - safely compute an object's annotations
"""
# This module is in the public domain. No warranties.
@@ -60,6 +62,122 @@
# See Include/object.h
TPFLAGS_IS_ABSTRACT = 1 << 20
+
+def get_annotations(obj, *, globals=None, locals=None, eval_str=False):
+ """Compute the annotations dict for an object.
+
+ obj may be a callable, class, or module.
+ Passing in an object of any other type raises TypeError.
+
+ Returns a dict. get_annotations() returns a new dict every time
+ it's called; calling it twice on the same object will return two
+ different but equivalent dicts.
+
+ This function handles several details for you:
+
+ * If eval_str is true, values of type str will
+ be un-stringized using eval(). This is intended
+ for use with stringized annotations
+ ("from __future__ import annotations").
+ * If obj doesn't have an annotations dict, returns an
+ empty dict. (Functions and methods always have an
+ annotations dict; classes, modules, and other types of
+ callables may not.)
+ * Ignores inherited annotations on classes. If a class
+ doesn't have its own annotations dict, returns an empty dict.
+ * All accesses to object members and dict values are done
+ using getattr() and dict.get() for safety.
+ * Always, always, always returns a freshly-created dict.
+
+ eval_str controls whether or not values of type str are replaced
+ with the result of calling eval() on those values:
+
+ * If eval_str is true, eval() is called on values of type str.
+ * If eval_str is false (the default), values of type str are unchanged.
+
+ globals and locals are passed in to eval(); see the documentation
+ for eval() for more information. If either globals or locals is
+ None, this function may replace that value with a context-specific
+ default, contingent on type(obj):
+
+ * If obj is a module, globals defaults to obj.__dict__.
+ * If obj is a class, globals defaults to
+ sys.modules[obj.__module__].__dict__ and locals
+ defaults to the obj class namespace.
+ * If obj is a callable, globals defaults to obj.__globals__,
+ although if obj is a wrapped function (using
+ functools.update_wrapper()) it is first unwrapped.
+ """
+ if isinstance(obj, type):
+ # class
+ obj_dict = getattr(obj, '__dict__', None)
+ if obj_dict and hasattr(obj_dict, 'get'):
+ ann = obj_dict.get('__annotations__', None)
+ if isinstance(ann, types.GetSetDescriptorType):
+ ann = None
+ else:
+ ann = None
+
+ obj_globals = None
+ module_name = getattr(obj, '__module__', None)
+ if module_name:
+ module = sys.modules.get(module_name, None)
+ if module:
+ obj_globals = getattr(module, '__dict__', None)
+ obj_locals = dict(vars(obj))
+ unwrap = obj
+ elif isinstance(obj, types.ModuleType):
+ # module
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__dict__')
+ obj_locals = None
+ unwrap = None
+ elif callable(obj):
+ # this includes types.Function, types.BuiltinFunctionType,
+ # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+ # "class funclike" from Lib/test/test_inspect... on and on it goes.
+ ann = getattr(obj, '__annotations__', None)
+ obj_globals = getattr(obj, '__globals__', None)
+ obj_locals = None
+ unwrap = obj
+ else:
+ raise TypeError(f"{obj!r} is not a module, class, or callable.")
+
+ if ann is None:
+ return {}
+
+ if not isinstance(ann, dict):
+ raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
+
+ if not ann:
+ return {}
+
+ if not eval_str:
+ return dict(ann)
+
+ if unwrap is not None:
+ while True:
+ if hasattr(unwrap, '__wrapped__'):
+ unwrap = unwrap.__wrapped__
+ continue
+ if isinstance(unwrap, functools.partial):
+ unwrap = unwrap.func
+ continue
+ break
+ if hasattr(unwrap, "__globals__"):
+ obj_globals = unwrap.__globals__
+
+ if globals is None:
+ globals = obj_globals
+ if locals is None:
+ locals = obj_locals
+
+ return_value = {key:
+ value if not isinstance(value, str) else eval(value, globals, locals)
+ for key, value in ann.items() }
+ return return_value
+
+
# ----------------------------------------------------------- type-checking
def ismodule(object):
"""Return true if the object is a module.
@@ -1165,7 +1283,8 @@ def getfullargspec(func):
sig = _signature_from_callable(func,
follow_wrapper_chains=False,
skip_bound_arg=False,
- sigcls=Signature)
+ sigcls=Signature,
+ eval_str=False)
except Exception as ex:
# Most of the times 'signature' will raise ValueError.
# But, it can also raise AttributeError, and, maybe something
@@ -1898,7 +2017,7 @@ def _signature_is_functionlike(obj):
isinstance(name, str) and
(defaults is None or isinstance(defaults, tuple)) and
(kwdefaults is None or isinstance(kwdefaults, dict)) and
- isinstance(annotations, dict))
+ (isinstance(annotations, (dict)) or annotations is None) )
def _signature_get_bound_param(spec):
@@ -2151,7 +2270,7 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
def _signature_from_function(cls, func, skip_bound_arg=True,
- globalns=None, localns=None):
+ globals=None, locals=None, eval_str=False):
"""Private helper: constructs Signature for the given python function."""
is_duck_function = False
@@ -2177,7 +2296,7 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
- annotations = func.__annotations__
+ annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str)
defaults = func.__defaults__
kwdefaults = func.__kwdefaults__
@@ -2248,8 +2367,9 @@ def _signature_from_function(cls, func, skip_bound_arg=True,
def _signature_from_callable(obj, *,
follow_wrapper_chains=True,
skip_bound_arg=True,
- globalns=None,
- localns=None,
+ globals=None,
+ locals=None,
+ eval_str=False,
sigcls):
"""Private helper function to get signature for arbitrary
@@ -2259,9 +2379,10 @@ def _signature_from_callable(obj, *,
_get_signature_of = functools.partial(_signature_from_callable,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
- globalns=globalns,
- localns=localns,
- sigcls=sigcls)
+ globals=globals,
+ locals=locals,
+ sigcls=sigcls,
+ eval_str=eval_str)
if not callable(obj):
raise TypeError('{!r} is not a callable object'.format(obj))
@@ -2330,7 +2451,7 @@ def _signature_from_callable(obj, *,
# of a Python function (Cython functions, for instance), then:
return _signature_from_function(sigcls, obj,
skip_bound_arg=skip_bound_arg,
- globalns=globalns, localns=localns)
+ globals=globals, locals=locals, eval_str=eval_str)
if _signature_is_builtin(obj):
return _signature_from_builtin(sigcls, obj,
@@ -2854,11 +2975,11 @@ def from_builtin(cls, func):
@classmethod
def from_callable(cls, obj, *,
- follow_wrapped=True, globalns=None, localns=None):
+ follow_wrapped=True, globals=None, locals=None, eval_str=False):
"""Constructs Signature for the given callable object."""
return _signature_from_callable(obj, sigcls=cls,
follow_wrapper_chains=follow_wrapped,
- globalns=globalns, localns=localns)
+ globals=globals, locals=locals, eval_str=eval_str)
@property
def parameters(self):
@@ -3106,10 +3227,10 @@ def __str__(self):
return rendered
-def signature(obj, *, follow_wrapped=True, globalns=None, localns=None):
+def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False):
"""Get a signature object for the passed callable."""
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
- globalns=globalns, localns=localns)
+ globals=globals, locals=locals, eval_str=eval_str)
def _main():
diff --git a/Lib/test/inspect_stock_annotations.py b/Lib/test/inspect_stock_annotations.py
new file mode 100644
index 0000000000000..d115a25b65034
--- /dev/null
+++ b/Lib/test/inspect_stock_annotations.py
@@ -0,0 +1,28 @@
+a:int=3
+b:str="foo"
+
+class MyClass:
+ a:int=4
+ b:str="bar"
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+ def __eq__(self, other):
+ return isinstance(other, MyClass) and self.a == other.a and self.b == other.b
+
+def function(a:int, b:str) -> MyClass:
+ return MyClass(a, b)
+
+
+def function2(a:int, b:"str", c:MyClass) -> MyClass:
+ pass
+
+
+def function3(a:"int", b:"str", c:"MyClass"):
+ pass
+
+
+class UnannotatedClass:
+ pass
+
+def unannotated_function(a, b, c): pass
diff --git a/Lib/test/inspect_stringized_annotations.py b/Lib/test/inspect_stringized_annotations.py
new file mode 100644
index 0000000000000..a56fb050ead97
--- /dev/null
+++ b/Lib/test/inspect_stringized_annotations.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+a:int=3
+b:str="foo"
+
+class MyClass:
+ a:int=4
+ b:str="bar"
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+ def __eq__(self, other):
+ return isinstance(other, MyClass) and self.a == other.a and self.b == other.b
+
+def function(a:int, b:str) -> MyClass:
+ return MyClass(a, b)
+
+
+def function2(a:int, b:"str", c:MyClass) -> MyClass:
+ pass
+
+
+def function3(a:"int", b:"str", c:"MyClass"):
+ pass
+
+
+class UnannotatedClass:
+ pass
+
+def unannotated_function(a, b, c): pass
+
+class MyClassWithLocalAnnotations:
+ mytype = int
+ x: mytype
diff --git a/Lib/test/inspect_stringized_annotations_2.py b/Lib/test/inspect_stringized_annotations_2.py
new file mode 100644
index 0000000000000..87206d5a64692
--- /dev/null
+++ b/Lib/test/inspect_stringized_annotations_2.py
@@ -0,0 +1,3 @@
+from __future__ import annotations
+
+def foo(a, b, c): pass
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index b32b3d3757750..0ab65307cdd09 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -32,6 +32,9 @@
from test import inspect_fodder as mod
from test import inspect_fodder2 as mod2
from test import support
+from test import inspect_stock_annotations
+from test import inspect_stringized_annotations
+from test import inspect_stringized_annotations_2
from test.test_import import _ready_to_import
@@ -1281,6 +1284,106 @@ class C(metaclass=M):
attrs = [a[0] for a in inspect.getmembers(C)]
self.assertNotIn('missing', attrs)
+ def test_get_annotations_with_stock_annotations(self):
+ def foo(a:int, b:str): pass
+ self.assertEqual(inspect.get_annotations(foo), {'a': int, 'b': str})
+
+ foo.__annotations__ = {'a': 'foo', 'b':'str'}
+ self.assertEqual(inspect.get_annotations(foo), {'a': 'foo', 'b': 'str'})
+
+ self.assertEqual(inspect.get_annotations(foo, eval_str=True, locals=locals()), {'a': foo, 'b': str})
+ self.assertEqual(inspect.get_annotations(foo, eval_str=True, globals=locals()), {'a': foo, 'b': str})
+
+ isa = inspect_stock_annotations
+ self.assertEqual(inspect.get_annotations(isa), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3), {'a': 'int', 'b': 'str', 'c': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(inspect), {}) # inspect module has no annotations
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=True), {'a': int, 'b': str, 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=True), {'a': int, 'b': str, 'c': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(inspect, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=True), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=False), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=False), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=False), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=False), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=False), {'a': 'int', 'b': 'str', 'c': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(inspect, eval_str=False), {})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=False), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=False), {})
+
+ def times_three(fn):
+ @functools.wraps(fn)
+ def wrapper(a, b):
+ return fn(a*3, b*3)
+ return wrapper
+
+ wrapped = times_three(isa.function)
+ self.assertEqual(wrapped(1, 'x'), isa.MyClass(3, 'xxx'))
+ self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
+ self.assertEqual(inspect.get_annotations(wrapped), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=False), {'a': int, 'b': str, 'return': isa.MyClass})
+
+ def test_get_annotations_with_stringized_annotations(self):
+ isa = inspect_stringized_annotations
+ self.assertEqual(inspect.get_annotations(isa), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.MyClass), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.function), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function2), {'a': 'int', 'b': "'str'", 'c': 'MyClass', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function3), {'a': "'int'", 'b': "'str'", 'c': "'MyClass'"})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=True), {'a': int, 'b': str})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=True), {'a': int, 'b': 'str', 'c': isa.MyClass, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=True), {'a': 'int', 'b': 'str', 'c': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=True), {})
+
+ self.assertEqual(inspect.get_annotations(isa, eval_str=False), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.MyClass, eval_str=False), {'a': 'int', 'b': 'str'})
+ self.assertEqual(inspect.get_annotations(isa.function, eval_str=False), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function2, eval_str=False), {'a': 'int', 'b': "'str'", 'c': 'MyClass', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(isa.function3, eval_str=False), {'a': "'int'", 'b': "'str'", 'c': "'MyClass'"})
+ self.assertEqual(inspect.get_annotations(isa.UnannotatedClass, eval_str=False), {})
+ self.assertEqual(inspect.get_annotations(isa.unannotated_function, eval_str=False), {})
+
+ isa2 = inspect_stringized_annotations_2
+ self.assertEqual(inspect.get_annotations(isa2), {})
+ self.assertEqual(inspect.get_annotations(isa2, eval_str=True), {})
+ self.assertEqual(inspect.get_annotations(isa2, eval_str=False), {})
+
+ def times_three(fn):
+ @functools.wraps(fn)
+ def wrapper(a, b):
+ return fn(a*3, b*3)
+ return wrapper
+
+ wrapped = times_three(isa.function)
+ self.assertEqual(wrapped(1, 'x'), isa.MyClass(3, 'xxx'))
+ self.assertIsNot(wrapped.__globals__, isa.function.__globals__)
+ self.assertEqual(inspect.get_annotations(wrapped), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=True), {'a': int, 'b': str, 'return': isa.MyClass})
+ self.assertEqual(inspect.get_annotations(wrapped, eval_str=False), {'a': 'int', 'b': 'str', 'return': 'MyClass'})
+
+ # test that local namespace lookups work
+ self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations), {'x': 'mytype'})
+ self.assertEqual(inspect.get_annotations(isa.MyClassWithLocalAnnotations, eval_str=True), {'x': int})
+
+
class TestIsDataDescriptor(unittest.TestCase):
def test_custom_descriptors(self):
@@ -2786,13 +2889,13 @@ def test(it, a, *, c) -> 'spam':
pass
ham = partialmethod(test, c=1)
- self.assertEqual(self.signature(Spam.ham),
+ self.assertEqual(self.signature(Spam.ham, eval_str=False),
((('it', ..., ..., 'positional_or_keyword'),
('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')),
'spam'))
- self.assertEqual(self.signature(Spam().ham),
+ self.assertEqual(self.signature(Spam().ham, eval_str=False),
((('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')),
'spam'))
@@ -2803,7 +2906,7 @@ def test(self: 'anno', x):
g = partialmethod(test, 1)
- self.assertEqual(self.signature(Spam.g),
+ self.assertEqual(self.signature(Spam.g, eval_str=False),
((('self', ..., 'anno', 'positional_or_keyword'),),
...))
@@ -3265,15 +3368,145 @@ def func2(foo: Foo, bar: 'Bar') -> int: pass
self.assertEqual(sig1.return_annotation, int)
self.assertEqual(sig1.parameters['foo'].annotation, Foo)
- sig2 = signature_func(func, localns=locals())
+ sig2 = signature_func(func, locals=locals())
self.assertEqual(sig2.return_annotation, int)
self.assertEqual(sig2.parameters['foo'].annotation, Foo)
- sig3 = signature_func(func2, globalns={'Bar': int}, localns=locals())
+ sig3 = signature_func(func2, globals={'Bar': int}, locals=locals())
self.assertEqual(sig3.return_annotation, int)
self.assertEqual(sig3.parameters['foo'].annotation, Foo)
self.assertEqual(sig3.parameters['bar'].annotation, 'Bar')
+ def test_signature_eval_str(self):
+ isa = inspect_stringized_annotations
+ sig = inspect.Signature
+ par = inspect.Parameter
+ PORK = inspect.Parameter.POSITIONAL_OR_KEYWORD
+ for signature_func in (inspect.signature, inspect.Signature.from_callable):
+ with self.subTest(signature_func = signature_func):
+ self.assertEqual(
+ signature_func(isa.MyClass),
+ sig(
+ parameters=(
+ par('a', PORK),
+ par('b', PORK),
+ )))
+ self.assertEqual(
+ signature_func(isa.function),
+ sig(
+ return_annotation='MyClass',
+ parameters=(
+ par('a', PORK, annotation='int'),
+ par('b', PORK, annotation='str'),
+ )))
+ self.assertEqual(
+ signature_func(isa.function2),
+ sig(
+ return_annotation='MyClass',
+ parameters=(
+ par('a', PORK, annotation='int'),
+ par('b', PORK, annotation="'str'"),
+ par('c', PORK, annotation="MyClass"),
+ )))
+ self.assertEqual(
+ signature_func(isa.function3),
+ sig(
+ parameters=(
+ par('a', PORK, annotation="'int'"),
+ par('b', PORK, annotation="'str'"),
+ par('c', PORK, annotation="'MyClass'"),
+ )))
+
+ self.assertEqual(signature_func(isa.UnannotatedClass), sig())
+ self.assertEqual(signature_func(isa.unannotated_function),
+ sig(
+ parameters=(
+ par('a', PORK),
+ par('b', PORK),
+ par('c', PORK),
+ )))
+
+ self.assertEqual(
+ signature_func(isa.MyClass, eval_str=True),
+ sig(
+ parameters=(
+ par('a', PORK),
+ par('b', PORK),
+ )))
+ self.assertEqual(
+ signature_func(isa.function, eval_str=True),
+ sig(
+ return_annotation=isa.MyClass,
+ parameters=(
+ par('a', PORK, annotation=int),
+ par('b', PORK, annotation=str),
+ )))
+ self.assertEqual(
+ signature_func(isa.function2, eval_str=True),
+ sig(
+ return_annotation=isa.MyClass,
+ parameters=(
+ par('a', PORK, annotation=int),
+ par('b', PORK, annotation='str'),
+ par('c', PORK, annotation=isa.MyClass),
+ )))
+ self.assertEqual(
+ signature_func(isa.function3, eval_str=True),
+ sig(
+ parameters=(
+ par('a', PORK, annotation='int'),
+ par('b', PORK, annotation='str'),
+ par('c', PORK, annotation='MyClass'),
+ )))
+
+ globalns = {'int': float, 'str': complex}
+ localns = {'str': tuple, 'MyClass': dict}
+ with self.assertRaises(NameError):
+ signature_func(isa.function, eval_str=True, globals=globalns)
+
+ self.assertEqual(
+ signature_func(isa.function, eval_str=True, locals=localns),
+ sig(
+ return_annotation=dict,
+ parameters=(
+ par('a', PORK, annotation=int),
+ par('b', PORK, annotation=tuple),
+ )))
+
+ self.assertEqual(
+ signature_func(isa.function, eval_str=True, globals=globalns, locals=localns),
+ sig(
+ return_annotation=dict,
+ parameters=(
+ par('a', PORK, annotation=float),
+ par('b', PORK, annotation=tuple),
+ )))
+
+ def test_signature_none_annotation(self):
+ class funclike:
+ # Has to be callable, and have correct
+ # __code__, __annotations__, __defaults__, __name__,
+ # and __kwdefaults__ attributes
+
+ def __init__(self, func):
+ self.__name__ = func.__name__
+ self.__code__ = func.__code__
+ self.__annotations__ = func.__annotations__
+ self.__defaults__ = func.__defaults__
+ self.__kwdefaults__ = func.__kwdefaults__
+ self.func = func
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ def foo(): pass
+ foo = funclike(foo)
+ foo.__annotations__ = None
+ for signature_func in (inspect.signature, inspect.Signature.from_callable):
+ with self.subTest(signature_func = signature_func):
+ self.assertEqual(signature_func(foo), inspect.Signature())
+ self.assertEqual(inspect.get_annotations(foo), {})
+
class TestParameterObject(unittest.TestCase):
def test_signature_parameter_kinds(self):
diff --git a/Misc/NEWS.d/next/Library/2021-04-22-04-12-13.bpo-43817.FQ-XlH.rst b/Misc/NEWS.d/next/Library/2021-04-22-04-12-13.bpo-43817.FQ-XlH.rst
new file mode 100644
index 0000000000000..36a6018bab253
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-04-22-04-12-13.bpo-43817.FQ-XlH.rst
@@ -0,0 +1,11 @@
+Add :func:`inspect.get_annotations`, which safely computes the annotations
+defined on an object. It works around the quirks of accessing the
+annotations from various types of objects, and makes very few assumptions
+about the object passed in. :func:`inspect.get_annotations` can also
+correctly un-stringize stringized annotations.
+
+:func:`inspect.signature`, :func:`inspect.from_callable`, and
+:func:`inspect.from_function` now call :func:`inspect.get_annotations`
+to retrieve annotations. This means :func:`inspect.signature`
+and :func:`inspect.from_callable` can now un-stringize stringized
+annotations, too.
1
0
https://github.com/python/cpython/commit/a62e424de0c394cda178a8d934d06f0559…
commit: a62e424de0c394cda178a8d934d06f0559b5e28d
branch: master
author: Terry Jan Reedy <tjreedy(a)udel.edu>
committer: terryjreedy <tjreedy(a)udel.edu>
date: 2021-04-29T23:52:47-04:00
summary:
bpo-43981: Fix error in idle-test leak test (GH-25739)
Remove call to macosx.setupApp, which calls macosc.overrideRootMenu, which modifies
the menus, which results in two failures in the second round of the leak test.
files:
M Lib/idlelib/idle_test/test_sidebar.py
diff --git a/Lib/idlelib/idle_test/test_sidebar.py b/Lib/idlelib/idle_test/test_sidebar.py
index 7228d0ee731fa..0497f6d05139e 100644
--- a/Lib/idlelib/idle_test/test_sidebar.py
+++ b/Lib/idlelib/idle_test/test_sidebar.py
@@ -7,11 +7,10 @@
import unittest.mock
from test.support import requires, swap_attr
import tkinter as tk
-from .tkinter_testing_utils import run_in_tk_mainloop
+from idlelib.idle_test.tkinter_testing_utils import run_in_tk_mainloop
from idlelib.delegator import Delegator
from idlelib.editor import fixwordbreaks
-from idlelib import macosx
from idlelib.percolator import Percolator
import idlelib.pyshell
from idlelib.pyshell import fix_x11_paste, PyShell, PyShellFileList
@@ -408,7 +407,7 @@ def setUpClass(cls):
fix_x11_paste(root)
cls.flist = flist = PyShellFileList(root)
- macosx.setupApp(root, flist)
+ # See #43981 about macosx.setupApp(root, flist) causing failure.
root.update_idletasks()
cls.init_shell()
1
0
https://github.com/python/cpython/commit/175a54b2d8f967605f1d46b5cadccdcf2b…
commit: 175a54b2d8f967605f1d46b5cadccdcf2b54842f
branch: master
author: larryhastings <larry(a)hastings.org>
committer: larryhastings <larry(a)hastings.org>
date: 2021-04-29T20:13:25-07:00
summary:
Two minor fixes for accessing a module's name. (#25658)
While working on another issue, I noticed two minor nits in the C implementation of the module object. Both are related to getting a module's name.
First, the C function module_dir() (module.__dir__) starts by ensuring the module dict is valid. If the module dict is invalid, it wants to format an exception using the name of the module, which it gets from PyModule_GetName(). However, PyModule_GetName() gets the name of the module from the dict. So getting the name in this circumstance will never succeed.
When module_dir() wants to format the error but can't get the name, it knows that PyModule_GetName() must have already raised an exception. So it leaves that exception alone and returns an error. The end result is that the exception raised here is kind of useless and misleading: dir(module) on a module with no __dict__ raises SystemError("nameless module"). I changed the code to actually raise the exception it wanted to raise, just without a real module name: TypeError("<module>.__dict__ is not a dictionary"). This seems more useful, and would do a better job putting the programmer who encountered this on the right track of figuring out what was going on.
Second, the C API function PyModule_GetNameObject() checks to see if the module has a dict. If m->md_dict is not NULL, it calls _PyDict_GetItemIdWithError(). However, it's possible for m->md_dict to be None. And if you call _PyDict_GetItemIdWithError(Py_None, ...) it will *crash*.
Unfortunately, this crash was due to my own bug in the other branch. Fixing my code made the crash go away. I assert that this is still possible at the API level.
The fix is easy: add a PyDict_Check() to PyModule_GetNameObject().
Unfortunately, I don't know how to add a unit test for this. Having changed module_dir() above, I can't find any other interfaces callable from Python that eventually call PyModule_GetNameObject(). So I don't know how to trick the runtime into reproducing this error.
Since both these changes are minor--each entails only a small edit to only one line--I didn't bother with a news item.
files:
M Lib/test/test_module.py
M Objects/moduleobject.c
diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py
index aa5ee49854059..0fd82ea713f7c 100644
--- a/Lib/test/test_module.py
+++ b/Lib/test/test_module.py
@@ -22,7 +22,7 @@ def test_uninitialized(self):
# and __doc__ is None
foo = ModuleType.__new__(ModuleType)
self.assertTrue(foo.__dict__ is None)
- self.assertRaises(SystemError, dir, foo)
+ self.assertRaises(TypeError, dir, foo)
try:
s = foo.__name__
self.fail("__name__ = %s" % repr(s))
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index cdb365d29a914..04346f7ad1067 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -481,7 +481,7 @@ PyModule_GetNameObject(PyObject *m)
return NULL;
}
d = ((PyModuleObject *)m)->md_dict;
- if (d == NULL ||
+ if (d == NULL || !PyDict_Check(d) ||
(name = _PyDict_GetItemIdWithError(d, &PyId___name__)) == NULL ||
!PyUnicode_Check(name))
{
@@ -824,11 +824,7 @@ module_dir(PyObject *self, PyObject *args)
}
}
else {
- const char *name = PyModule_GetName(self);
- if (name)
- PyErr_Format(PyExc_TypeError,
- "%.200s.__dict__ is not a dictionary",
- name);
+ PyErr_Format(PyExc_TypeError, "<module>.__dict__ is not a dictionary");
}
}
1
0
bpo-43901: Lazy-create an empty annotations dict in all unannotated user classes and modules (#25623)
by larryhastings 30 Apr '21
by larryhastings 30 Apr '21
30 Apr '21
https://github.com/python/cpython/commit/2f2b69855d6524e15d12c15ddc0adce629…
commit: 2f2b69855d6524e15d12c15ddc0adce629e7de84
branch: master
author: larryhastings <larry(a)hastings.org>
committer: larryhastings <larry(a)hastings.org>
date: 2021-04-29T20:09:08-07:00
summary:
bpo-43901: Lazy-create an empty annotations dict in all unannotated user classes and modules (#25623)
Change class and module objects to lazy-create empty annotations dicts on demand. The annotations dicts are stored in the object's `__dict__` for backwards compatibility.
files:
A Lib/test/ann_module4.py
A Lib/test/test_type_annotations.py
A Misc/NEWS.d/next/Core and Builtins/2021-04-25-22-50-47.bpo-43901.oKjG5E.rst
M Lib/test/test_grammar.py
M Lib/test/test_module.py
M Lib/test/test_opcodes.py
M Lib/typing.py
M Objects/moduleobject.c
M Objects/typeobject.c
diff --git a/Lib/test/ann_module4.py b/Lib/test/ann_module4.py
new file mode 100644
index 0000000000000..13e9aee54c98b
--- /dev/null
+++ b/Lib/test/ann_module4.py
@@ -0,0 +1,5 @@
+# This ann_module isn't for test_typing,
+# it's for test_module
+
+a:int=3
+b:str=4
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index 6f79e19a54435..46f70e5d176fc 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -382,8 +382,7 @@ class CC(metaclass=CMeta):
self.assertEqual(CC.__annotations__['xx'], 'ANNOT')
def test_var_annot_module_semantics(self):
- with self.assertRaises(AttributeError):
- print(test.__annotations__)
+ self.assertEqual(test.__annotations__, {})
self.assertEqual(ann_module.__annotations__,
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]})
self.assertEqual(ann_module.M.__annotations__,
diff --git a/Lib/test/test_module.py b/Lib/test/test_module.py
index 1d44563579fd2..aa5ee49854059 100644
--- a/Lib/test/test_module.py
+++ b/Lib/test/test_module.py
@@ -286,6 +286,60 @@ class M(ModuleType):
melon = Descr()
self.assertRaises(RuntimeError, getattr, M("mymod"), "melon")
+ def test_lazy_create_annotations(self):
+ # module objects lazy create their __annotations__ dict on demand.
+ # the annotations dict is stored in module.__dict__.
+ # a freshly created module shouldn't have an annotations dict yet.
+ foo = ModuleType("foo")
+ for i in range(4):
+ self.assertFalse("__annotations__" in foo.__dict__)
+ d = foo.__annotations__
+ self.assertTrue("__annotations__" in foo.__dict__)
+ self.assertEqual(foo.__annotations__, d)
+ self.assertEqual(foo.__dict__['__annotations__'], d)
+ if i % 2:
+ del foo.__annotations__
+ else:
+ del foo.__dict__['__annotations__']
+
+ def test_setting_annotations(self):
+ foo = ModuleType("foo")
+ for i in range(4):
+ self.assertFalse("__annotations__" in foo.__dict__)
+ d = {'a': int}
+ foo.__annotations__ = d
+ self.assertTrue("__annotations__" in foo.__dict__)
+ self.assertEqual(foo.__annotations__, d)
+ self.assertEqual(foo.__dict__['__annotations__'], d)
+ if i % 2:
+ del foo.__annotations__
+ else:
+ del foo.__dict__['__annotations__']
+
+ def test_annotations_getset_raises(self):
+ # module has no dict, all operations fail
+ foo = ModuleType.__new__(ModuleType)
+ with self.assertRaises(TypeError):
+ print(foo.__annotations__)
+ with self.assertRaises(TypeError):
+ foo.__annotations__ = {}
+ with self.assertRaises(TypeError):
+ del foo.__annotations__
+
+ # double delete
+ foo = ModuleType("foo")
+ foo.__annotations__ = {}
+ del foo.__annotations__
+ with self.assertRaises(AttributeError):
+ del foo.__annotations__
+
+ def test_annotations_are_created_correctly(self):
+ from test import ann_module4
+ self.assertTrue("__annotations__" in ann_module4.__dict__)
+ del ann_module4.__annotations__
+ self.assertFalse("__annotations__" in ann_module4.__dict__)
+
+
# frozen and namespace module reprs are tested in importlib.
diff --git a/Lib/test/test_opcodes.py b/Lib/test/test_opcodes.py
index d43a8303b1710..e880c3f1ac875 100644
--- a/Lib/test/test_opcodes.py
+++ b/Lib/test/test_opcodes.py
@@ -31,10 +31,9 @@ def test_setup_annotations_line(self):
except OSError:
pass
- def test_no_annotations_if_not_needed(self):
+ def test_default_annotations_exist(self):
class C: pass
- with self.assertRaises(AttributeError):
- C.__annotations__
+ self.assertEqual(C.__annotations__, {})
def test_use_existing_annotations(self):
ns = {'__annotations__': {1: 2}}
diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py
new file mode 100644
index 0000000000000..f6c99bda3aa6f
--- /dev/null
+++ b/Lib/test/test_type_annotations.py
@@ -0,0 +1,103 @@
+import unittest
+
+class TypeAnnotationTests(unittest.TestCase):
+
+ def test_lazy_create_annotations(self):
+ # type objects lazy create their __annotations__ dict on demand.
+ # the annotations dict is stored in type.__dict__.
+ # a freshly created type shouldn't have an annotations dict yet.
+ foo = type("Foo", (), {})
+ for i in range(3):
+ self.assertFalse("__annotations__" in foo.__dict__)
+ d = foo.__annotations__
+ self.assertTrue("__annotations__" in foo.__dict__)
+ self.assertEqual(foo.__annotations__, d)
+ self.assertEqual(foo.__dict__['__annotations__'], d)
+ del foo.__annotations__
+
+ def test_setting_annotations(self):
+ foo = type("Foo", (), {})
+ for i in range(3):
+ self.assertFalse("__annotations__" in foo.__dict__)
+ d = {'a': int}
+ foo.__annotations__ = d
+ self.assertTrue("__annotations__" in foo.__dict__)
+ self.assertEqual(foo.__annotations__, d)
+ self.assertEqual(foo.__dict__['__annotations__'], d)
+ del foo.__annotations__
+
+ def test_annotations_getset_raises(self):
+ # builtin types don't have __annotations__ (yet!)
+ with self.assertRaises(AttributeError):
+ print(float.__annotations__)
+ with self.assertRaises(TypeError):
+ float.__annotations__ = {}
+ with self.assertRaises(TypeError):
+ del float.__annotations__
+
+ # double delete
+ foo = type("Foo", (), {})
+ foo.__annotations__ = {}
+ del foo.__annotations__
+ with self.assertRaises(AttributeError):
+ del foo.__annotations__
+
+ def test_annotations_are_created_correctly(self):
+ class C:
+ a:int=3
+ b:str=4
+ self.assertTrue("__annotations__" in C.__dict__)
+ del C.__annotations__
+ self.assertFalse("__annotations__" in C.__dict__)
+
+ def test_descriptor_still_works(self):
+ class C:
+ def __init__(self, name=None, bases=None, d=None):
+ self.my_annotations = None
+
+ @property
+ def __annotations__(self):
+ if not hasattr(self, 'my_annotations'):
+ self.my_annotations = {}
+ if not isinstance(self.my_annotations, dict):
+ self.my_annotations = {}
+ return self.my_annotations
+
+ @__annotations__.setter
+ def __annotations__(self, value):
+ if not isinstance(value, dict):
+ raise ValueError("can only set __annotations__ to a dict")
+ self.my_annotations = value
+
+ @__annotations__.deleter
+ def __annotations__(self):
+ if hasattr(self, 'my_annotations') and self.my_annotations == None:
+ raise AttributeError('__annotations__')
+ self.my_annotations = None
+
+ c = C()
+ self.assertEqual(c.__annotations__, {})
+ d = {'a':'int'}
+ c.__annotations__ = d
+ self.assertEqual(c.__annotations__, d)
+ with self.assertRaises(ValueError):
+ c.__annotations__ = 123
+ del c.__annotations__
+ with self.assertRaises(AttributeError):
+ del c.__annotations__
+ self.assertEqual(c.__annotations__, {})
+
+
+ class D(metaclass=C):
+ pass
+
+ self.assertEqual(D.__annotations__, {})
+ d = {'a':'int'}
+ D.__annotations__ = d
+ self.assertEqual(D.__annotations__, d)
+ with self.assertRaises(ValueError):
+ D.__annotations__ = 123
+ del D.__annotations__
+ with self.assertRaises(AttributeError):
+ del D.__annotations__
+ self.assertEqual(D.__annotations__, {})
diff --git a/Lib/typing.py b/Lib/typing.py
index d409517ff58e9..ff964343c5336 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1677,6 +1677,8 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
else:
base_globals = globalns
ann = base.__dict__.get('__annotations__', {})
+ if isinstance(ann, types.GetSetDescriptorType):
+ ann = {}
base_locals = dict(vars(base)) if localns is None else localns
if localns is None and globalns is None:
# This is surprising, but required. Before Python 3.10,
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-04-25-22-50-47.bpo-43901.oKjG5E.rst b/Misc/NEWS.d/next/Core and Builtins/2021-04-25-22-50-47.bpo-43901.oKjG5E.rst
new file mode 100644
index 0000000000000..2ab93d1f756f6
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-04-25-22-50-47.bpo-43901.oKjG5E.rst
@@ -0,0 +1,3 @@
+Change class and module objects to lazy-create empty annotations dicts on
+demand. The annotations dicts are stored in the object's __dict__ for
+backwards compatibility.
diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c
index a6eb85bdc2a96..cdb365d29a914 100644
--- a/Objects/moduleobject.c
+++ b/Objects/moduleobject.c
@@ -12,6 +12,9 @@ static Py_ssize_t max_module_number;
_Py_IDENTIFIER(__doc__);
_Py_IDENTIFIER(__name__);
_Py_IDENTIFIER(__spec__);
+_Py_IDENTIFIER(__dict__);
+_Py_IDENTIFIER(__dir__);
+_Py_IDENTIFIER(__annotations__);
static PyMemberDef module_members[] = {
{"__dict__", T_OBJECT, offsetof(PyModuleObject, md_dict), READONLY},
@@ -807,8 +810,6 @@ module_clear(PyModuleObject *m)
static PyObject *
module_dir(PyObject *self, PyObject *args)
{
- _Py_IDENTIFIER(__dict__);
- _Py_IDENTIFIER(__dir__);
PyObject *result = NULL;
PyObject *dict = _PyObject_GetAttrId(self, &PyId___dict__);
@@ -841,6 +842,71 @@ static PyMethodDef module_methods[] = {
{0}
};
+static PyObject *
+module_get_annotations(PyModuleObject *m, void *Py_UNUSED(ignored))
+{
+ PyObject *dict = _PyObject_GetAttrId((PyObject *)m, &PyId___dict__);
+
+ if ((dict == NULL) || !PyDict_Check(dict)) {
+ PyErr_Format(PyExc_TypeError, "<module>.__dict__ is not a dictionary");
+ return NULL;
+ }
+
+ PyObject *annotations;
+ /* there's no _PyDict_GetItemId without WithError, so let's LBYL. */
+ if (_PyDict_ContainsId(dict, &PyId___annotations__)) {
+ annotations = _PyDict_GetItemIdWithError(dict, &PyId___annotations__);
+ /*
+ ** _PyDict_GetItemIdWithError could still fail,
+ ** for instance with a well-timed Ctrl-C or a MemoryError.
+ ** so let's be totally safe.
+ */
+ if (annotations) {
+ Py_INCREF(annotations);
+ }
+ } else {
+ annotations = PyDict_New();
+ if (annotations) {
+ int result = _PyDict_SetItemId(dict, &PyId___annotations__, annotations);
+ if (result) {
+ Py_CLEAR(annotations);
+ }
+ }
+ }
+ Py_DECREF(dict);
+ return annotations;
+}
+
+static int
+module_set_annotations(PyModuleObject *m, PyObject *value, void *Py_UNUSED(ignored))
+{
+ PyObject *dict = _PyObject_GetAttrId((PyObject *)m, &PyId___dict__);
+
+ if ((dict == NULL) || !PyDict_Check(dict)) {
+ PyErr_Format(PyExc_TypeError, "<module>.__dict__ is not a dictionary");
+ return -1;
+ }
+
+ if (value != NULL) {
+ /* set */
+ return _PyDict_SetItemId(dict, &PyId___annotations__, value);
+ }
+
+ /* delete */
+ if (!_PyDict_ContainsId(dict, &PyId___annotations__)) {
+ PyErr_Format(PyExc_AttributeError, "__annotations__");
+ return -1;
+ }
+
+ return _PyDict_DelItemId(dict, &PyId___annotations__);
+}
+
+
+static PyGetSetDef module_getsets[] = {
+ {"__annotations__", (getter)module_get_annotations, (setter)module_set_annotations},
+ {NULL}
+};
+
PyTypeObject PyModule_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"module", /* tp_name */
@@ -872,7 +938,7 @@ PyTypeObject PyModule_Type = {
0, /* tp_iternext */
module_methods, /* tp_methods */
module_members, /* tp_members */
- 0, /* tp_getset */
+ module_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index e1c8be4b81545..ac4dc1da4411d 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -52,6 +52,7 @@ typedef struct PySlot_Offset {
/* alphabetical order */
_Py_IDENTIFIER(__abstractmethods__);
+_Py_IDENTIFIER(__annotations__);
_Py_IDENTIFIER(__class__);
_Py_IDENTIFIER(__class_getitem__);
_Py_IDENTIFIER(__classcell__);
@@ -930,6 +931,73 @@ type_set_doc(PyTypeObject *type, PyObject *value, void *context)
return _PyDict_SetItemId(type->tp_dict, &PyId___doc__, value);
}
+static PyObject *
+type_get_annotations(PyTypeObject *type, void *context)
+{
+ if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+ PyErr_Format(PyExc_AttributeError, "type object '%s' has no attribute '__annotations__'", type->tp_name);
+ return NULL;
+ }
+
+ PyObject *annotations;
+ /* there's no _PyDict_GetItemId without WithError, so let's LBYL. */
+ if (_PyDict_ContainsId(type->tp_dict, &PyId___annotations__)) {
+ annotations = _PyDict_GetItemIdWithError(type->tp_dict, &PyId___annotations__);
+ /*
+ ** _PyDict_GetItemIdWithError could still fail,
+ ** for instance with a well-timed Ctrl-C or a MemoryError.
+ ** so let's be totally safe.
+ */
+ if (annotations) {
+ if (Py_TYPE(annotations)->tp_descr_get) {
+ annotations = Py_TYPE(annotations)->tp_descr_get(annotations, NULL,
+ (PyObject *)type);
+ } else {
+ Py_INCREF(annotations);
+ }
+ }
+ } else {
+ annotations = PyDict_New();
+ if (annotations) {
+ int result = _PyDict_SetItemId(type->tp_dict, &PyId___annotations__, annotations);
+ if (result) {
+ Py_CLEAR(annotations);
+ } else {
+ PyType_Modified(type);
+ }
+ }
+ }
+ return annotations;
+}
+
+static int
+type_set_annotations(PyTypeObject *type, PyObject *value, void *context)
+{
+ if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
+ PyErr_Format(PyExc_TypeError, "can't set attributes of built-in/extension type '%s'", type->tp_name);
+ return -1;
+ }
+
+ int result;
+ if (value != NULL) {
+ /* set */
+ result = _PyDict_SetItemId(type->tp_dict, &PyId___annotations__, value);
+ } else {
+ /* delete */
+ if (!_PyDict_ContainsId(type->tp_dict, &PyId___annotations__)) {
+ PyErr_Format(PyExc_AttributeError, "__annotations__");
+ return -1;
+ }
+ result = _PyDict_DelItemId(type->tp_dict, &PyId___annotations__);
+ }
+
+ if (result == 0) {
+ PyType_Modified(type);
+ }
+ return result;
+}
+
+
/*[clinic input]
type.__instancecheck__ -> bool
@@ -973,6 +1041,7 @@ static PyGetSetDef type_getsets[] = {
{"__dict__", (getter)type_dict, NULL, NULL},
{"__doc__", (getter)type_get_doc, (setter)type_set_doc, NULL},
{"__text_signature__", (getter)type_get_text_signature, NULL, NULL},
+ {"__annotations__", (getter)type_get_annotations, (setter)type_set_annotations, NULL},
{0}
};
1
0