mirror of https://github.com/python/cpython
Issue #21205: Add a new ``__qualname__`` attribute to generator, the qualified
name, and use it in the representation of a generator (``repr(gen)``). The default name of the generator (``__name__`` attribute) is now get from the function instead of the code. Use ``gen.gi_code.co_name`` to get the name of the code.
This commit is contained in:
parent
26171993fe
commit
40ee30181f
|
@ -159,6 +159,16 @@ attributes:
|
||||||
| | | arguments and local |
|
| | | arguments and local |
|
||||||
| | | variables |
|
| | | variables |
|
||||||
+-----------+-----------------+---------------------------+
|
+-----------+-----------------+---------------------------+
|
||||||
|
| generator | __name__ | name |
|
||||||
|
+-----------+-----------------+---------------------------+
|
||||||
|
| | __qualname__ | qualified name |
|
||||||
|
+-----------+-----------------+---------------------------+
|
||||||
|
| | gi_frame | frame |
|
||||||
|
+-----------+-----------------+---------------------------+
|
||||||
|
| | gi_running | is the generator running? |
|
||||||
|
+-----------+-----------------+---------------------------+
|
||||||
|
| | gi_code | code |
|
||||||
|
+-----------+-----------------+---------------------------+
|
||||||
| builtin | __doc__ | documentation string |
|
| builtin | __doc__ | documentation string |
|
||||||
+-----------+-----------------+---------------------------+
|
+-----------+-----------------+---------------------------+
|
||||||
| | __name__ | original name of this |
|
| | __name__ | original name of this |
|
||||||
|
@ -169,6 +179,10 @@ attributes:
|
||||||
| | | ``None`` |
|
| | | ``None`` |
|
||||||
+-----------+-----------------+---------------------------+
|
+-----------+-----------------+---------------------------+
|
||||||
|
|
||||||
|
.. versionchanged:: 3.5
|
||||||
|
|
||||||
|
Add ``__qualname__`` attribute to generators.
|
||||||
|
|
||||||
|
|
||||||
.. function:: getmembers(object[, predicate])
|
.. function:: getmembers(object[, predicate])
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,12 @@ Changes in the Python API
|
||||||
or :exc:`ssl.SSLWantWriteError` on a non-blocking socket if the operation
|
or :exc:`ssl.SSLWantWriteError` on a non-blocking socket if the operation
|
||||||
would block. Previously, it would return 0. See :issue:`20951`.
|
would block. Previously, it would return 0. See :issue:`20951`.
|
||||||
|
|
||||||
|
* The ``__name__`` attribute of generator is now set from the function name,
|
||||||
|
instead of being set from the code name. Use ``gen.gi_code.co_name`` to
|
||||||
|
retrieve the code name. Generators also have a new ``__qualname__``
|
||||||
|
attribute, the qualified name, which is now used for the representation
|
||||||
|
of a generator (``repr(gen)``). See :issue:`21205`.
|
||||||
|
|
||||||
Changes in the C API
|
Changes in the C API
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,12 @@ typedef struct {
|
||||||
|
|
||||||
/* List of weak reference. */
|
/* List of weak reference. */
|
||||||
PyObject *gi_weakreflist;
|
PyObject *gi_weakreflist;
|
||||||
|
|
||||||
|
/* Name of the generator. */
|
||||||
|
PyObject *gi_name;
|
||||||
|
|
||||||
|
/* Qualified name of the generator. */
|
||||||
|
PyObject *gi_qualname;
|
||||||
} PyGenObject;
|
} PyGenObject;
|
||||||
|
|
||||||
PyAPI_DATA(PyTypeObject) PyGen_Type;
|
PyAPI_DATA(PyTypeObject) PyGen_Type;
|
||||||
|
@ -33,6 +39,8 @@ PyAPI_DATA(PyTypeObject) PyGen_Type;
|
||||||
#define PyGen_CheckExact(op) (Py_TYPE(op) == &PyGen_Type)
|
#define PyGen_CheckExact(op) (Py_TYPE(op) == &PyGen_Type)
|
||||||
|
|
||||||
PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
|
PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
|
||||||
|
PyAPI_FUNC(PyObject *) PyGen_NewWithQualName(struct _frame *,
|
||||||
|
PyObject *name, PyObject *qualname);
|
||||||
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
|
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
|
||||||
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
|
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
|
||||||
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
|
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
|
||||||
|
|
|
@ -50,6 +50,45 @@ class FinalizationTest(unittest.TestCase):
|
||||||
self.assertEqual(gc.garbage, old_garbage)
|
self.assertEqual(gc.garbage, old_garbage)
|
||||||
|
|
||||||
|
|
||||||
|
class GeneratorTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_name(self):
|
||||||
|
def func():
|
||||||
|
yield 1
|
||||||
|
|
||||||
|
# check generator names
|
||||||
|
gen = func()
|
||||||
|
self.assertEqual(gen.__name__, "func")
|
||||||
|
self.assertEqual(gen.__qualname__,
|
||||||
|
"GeneratorTest.test_name.<locals>.func")
|
||||||
|
|
||||||
|
# modify generator names
|
||||||
|
gen.__name__ = "name"
|
||||||
|
gen.__qualname__ = "qualname"
|
||||||
|
self.assertEqual(gen.__name__, "name")
|
||||||
|
self.assertEqual(gen.__qualname__, "qualname")
|
||||||
|
|
||||||
|
# generator names must be a string and cannot be deleted
|
||||||
|
self.assertRaises(TypeError, setattr, gen, '__name__', 123)
|
||||||
|
self.assertRaises(TypeError, setattr, gen, '__qualname__', 123)
|
||||||
|
self.assertRaises(TypeError, delattr, gen, '__name__')
|
||||||
|
self.assertRaises(TypeError, delattr, gen, '__qualname__')
|
||||||
|
|
||||||
|
# modify names of the function creating the generator
|
||||||
|
func.__qualname__ = "func_qualname"
|
||||||
|
func.__name__ = "func_name"
|
||||||
|
gen = func()
|
||||||
|
self.assertEqual(gen.__name__, "func_name")
|
||||||
|
self.assertEqual(gen.__qualname__, "func_qualname")
|
||||||
|
|
||||||
|
# unnamed generator
|
||||||
|
gen = (x for x in range(10))
|
||||||
|
self.assertEqual(gen.__name__,
|
||||||
|
"<genexpr>")
|
||||||
|
self.assertEqual(gen.__qualname__,
|
||||||
|
"GeneratorTest.test_name.<locals>.<genexpr>")
|
||||||
|
|
||||||
|
|
||||||
tutorial_tests = """
|
tutorial_tests = """
|
||||||
Let's try a simple generator:
|
Let's try a simple generator:
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,12 @@ Release date: TBA
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #21205: Add a new ``__qualname__`` attribute to generator, the
|
||||||
|
qualified name, and use it in the representation of a generator
|
||||||
|
(``repr(gen)``). The default name of the generator (``__name__`` attribute)
|
||||||
|
is now get from the function instead of the code. Use ``gen.gi_code.co_name``
|
||||||
|
to get the name of the code.
|
||||||
|
|
||||||
- Issue #21669: With the aid of heuristics in SyntaxError.__init__, the
|
- Issue #21669: With the aid of heuristics in SyntaxError.__init__, the
|
||||||
parser now attempts to generate more meaningful (or at least more search
|
parser now attempts to generate more meaningful (or at least more search
|
||||||
engine friendly) error messages when "exec" and "print" are used as
|
engine friendly) error messages when "exec" and "print" are used as
|
||||||
|
|
|
@ -12,6 +12,8 @@ gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
|
||||||
{
|
{
|
||||||
Py_VISIT((PyObject *)gen->gi_frame);
|
Py_VISIT((PyObject *)gen->gi_frame);
|
||||||
Py_VISIT(gen->gi_code);
|
Py_VISIT(gen->gi_code);
|
||||||
|
Py_VISIT(gen->gi_name);
|
||||||
|
Py_VISIT(gen->gi_qualname);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +60,8 @@ gen_dealloc(PyGenObject *gen)
|
||||||
_PyObject_GC_UNTRACK(self);
|
_PyObject_GC_UNTRACK(self);
|
||||||
Py_CLEAR(gen->gi_frame);
|
Py_CLEAR(gen->gi_frame);
|
||||||
Py_CLEAR(gen->gi_code);
|
Py_CLEAR(gen->gi_code);
|
||||||
|
Py_CLEAR(gen->gi_name);
|
||||||
|
Py_CLEAR(gen->gi_qualname);
|
||||||
PyObject_GC_Del(gen);
|
PyObject_GC_Del(gen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,29 +422,69 @@ static PyObject *
|
||||||
gen_repr(PyGenObject *gen)
|
gen_repr(PyGenObject *gen)
|
||||||
{
|
{
|
||||||
return PyUnicode_FromFormat("<generator object %S at %p>",
|
return PyUnicode_FromFormat("<generator object %S at %p>",
|
||||||
((PyCodeObject *)gen->gi_code)->co_name,
|
gen->gi_qualname, gen);
|
||||||
gen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
gen_get_name(PyGenObject *gen)
|
gen_get_name(PyGenObject *op)
|
||||||
{
|
{
|
||||||
PyObject *name = ((PyCodeObject *)gen->gi_code)->co_name;
|
Py_INCREF(op->gi_name);
|
||||||
Py_INCREF(name);
|
return op->gi_name;
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gen_set_name(PyGenObject *op, PyObject *value)
|
||||||
|
{
|
||||||
|
PyObject *tmp;
|
||||||
|
|
||||||
PyDoc_STRVAR(gen__name__doc__,
|
/* Not legal to del gen.gi_name or to set it to anything
|
||||||
"Return the name of the generator's associated code object.");
|
* other than a string object. */
|
||||||
|
if (value == NULL || !PyUnicode_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"__name__ must be set to a string object");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
tmp = op->gi_name;
|
||||||
|
Py_INCREF(value);
|
||||||
|
op->gi_name = value;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
gen_get_qualname(PyGenObject *op)
|
||||||
|
{
|
||||||
|
Py_INCREF(op->gi_qualname);
|
||||||
|
return op->gi_qualname;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gen_set_qualname(PyGenObject *op, PyObject *value)
|
||||||
|
{
|
||||||
|
PyObject *tmp;
|
||||||
|
|
||||||
|
/* Not legal to del gen.__qualname__ or to set it to anything
|
||||||
|
* other than a string object. */
|
||||||
|
if (value == NULL || !PyUnicode_Check(value)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"__qualname__ must be set to a string object");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
tmp = op->gi_qualname;
|
||||||
|
Py_INCREF(value);
|
||||||
|
op->gi_qualname = value;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static PyGetSetDef gen_getsetlist[] = {
|
static PyGetSetDef gen_getsetlist[] = {
|
||||||
{"__name__", (getter)gen_get_name, NULL, gen__name__doc__},
|
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
|
||||||
{NULL}
|
PyDoc_STR("name of the generator")},
|
||||||
|
{"__qualname__", (getter)gen_get_qualname, (setter)gen_set_qualname,
|
||||||
|
PyDoc_STR("qualified name of the generator")},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static PyMemberDef gen_memberlist[] = {
|
static PyMemberDef gen_memberlist[] = {
|
||||||
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY},
|
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY},
|
||||||
{"gi_running", T_BOOL, offsetof(PyGenObject, gi_running), READONLY},
|
{"gi_running", T_BOOL, offsetof(PyGenObject, gi_running), READONLY},
|
||||||
|
@ -510,7 +554,7 @@ PyTypeObject PyGen_Type = {
|
||||||
};
|
};
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyGen_New(PyFrameObject *f)
|
PyGen_NewWithQualName(PyFrameObject *f, PyObject *name, PyObject *qualname)
|
||||||
{
|
{
|
||||||
PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);
|
PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);
|
||||||
if (gen == NULL) {
|
if (gen == NULL) {
|
||||||
|
@ -523,10 +567,26 @@ PyGen_New(PyFrameObject *f)
|
||||||
gen->gi_code = (PyObject *)(f->f_code);
|
gen->gi_code = (PyObject *)(f->f_code);
|
||||||
gen->gi_running = 0;
|
gen->gi_running = 0;
|
||||||
gen->gi_weakreflist = NULL;
|
gen->gi_weakreflist = NULL;
|
||||||
|
if (name != NULL)
|
||||||
|
gen->gi_name = name;
|
||||||
|
else
|
||||||
|
gen->gi_name = ((PyCodeObject *)gen->gi_code)->co_name;
|
||||||
|
Py_INCREF(gen->gi_name);
|
||||||
|
if (qualname != NULL)
|
||||||
|
gen->gi_qualname = qualname;
|
||||||
|
else
|
||||||
|
gen->gi_qualname = gen->gi_name;
|
||||||
|
Py_INCREF(gen->gi_qualname);
|
||||||
_PyObject_GC_TRACK(gen);
|
_PyObject_GC_TRACK(gen);
|
||||||
return (PyObject *)gen;
|
return (PyObject *)gen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
PyGen_New(PyFrameObject *f)
|
||||||
|
{
|
||||||
|
return PyGen_NewWithQualName(f, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
PyGen_NeedsFinalizing(PyGenObject *gen)
|
PyGen_NeedsFinalizing(PyGenObject *gen)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3401,10 +3401,11 @@ too_many_positional(PyCodeObject *co, int given, int defcount, PyObject **fastlo
|
||||||
PyEval_EvalFrame() and PyEval_EvalCodeEx() you will need to adjust
|
PyEval_EvalFrame() and PyEval_EvalCodeEx() you will need to adjust
|
||||||
the test in the if statements in Misc/gdbinit (pystack and pystackv). */
|
the test in the if statements in Misc/gdbinit (pystack and pystackv). */
|
||||||
|
|
||||||
PyObject *
|
static PyObject *
|
||||||
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
|
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
|
||||||
PyObject **args, int argcount, PyObject **kws, int kwcount,
|
PyObject **args, int argcount, PyObject **kws, int kwcount,
|
||||||
PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure)
|
PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure,
|
||||||
|
PyObject *name, PyObject *qualname)
|
||||||
{
|
{
|
||||||
PyCodeObject* co = (PyCodeObject*)_co;
|
PyCodeObject* co = (PyCodeObject*)_co;
|
||||||
PyFrameObject *f;
|
PyFrameObject *f;
|
||||||
|
@ -3596,7 +3597,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
|
||||||
|
|
||||||
/* Create a new generator that owns the ready to run frame
|
/* Create a new generator that owns the ready to run frame
|
||||||
* and return that as the value. */
|
* and return that as the value. */
|
||||||
return PyGen_New(f);
|
return PyGen_NewWithQualName(f, name, qualname);
|
||||||
}
|
}
|
||||||
|
|
||||||
retval = PyEval_EvalFrameEx(f,0);
|
retval = PyEval_EvalFrameEx(f,0);
|
||||||
|
@ -3615,6 +3616,16 @@ fail: /* Jump here from prelude on failure */
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
|
||||||
|
PyObject **args, int argcount, PyObject **kws, int kwcount,
|
||||||
|
PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure)
|
||||||
|
{
|
||||||
|
return _PyEval_EvalCodeWithName(_co, globals, locals,
|
||||||
|
args, argcount, kws, kwcount,
|
||||||
|
defs, defcount, kwdefs, closure,
|
||||||
|
NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
special_lookup(PyObject *o, _Py_Identifier *id)
|
special_lookup(PyObject *o, _Py_Identifier *id)
|
||||||
|
@ -4313,6 +4324,8 @@ fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
|
||||||
PyObject *globals = PyFunction_GET_GLOBALS(func);
|
PyObject *globals = PyFunction_GET_GLOBALS(func);
|
||||||
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
|
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
|
||||||
PyObject *kwdefs = PyFunction_GET_KW_DEFAULTS(func);
|
PyObject *kwdefs = PyFunction_GET_KW_DEFAULTS(func);
|
||||||
|
PyObject *name = ((PyFunctionObject *)func) -> func_name;
|
||||||
|
PyObject *qualname = ((PyFunctionObject *)func) -> func_qualname;
|
||||||
PyObject **d = NULL;
|
PyObject **d = NULL;
|
||||||
int nd = 0;
|
int nd = 0;
|
||||||
|
|
||||||
|
@ -4355,10 +4368,11 @@ fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
|
||||||
d = &PyTuple_GET_ITEM(argdefs, 0);
|
d = &PyTuple_GET_ITEM(argdefs, 0);
|
||||||
nd = Py_SIZE(argdefs);
|
nd = Py_SIZE(argdefs);
|
||||||
}
|
}
|
||||||
return PyEval_EvalCodeEx((PyObject*)co, globals,
|
return _PyEval_EvalCodeWithName((PyObject*)co, globals,
|
||||||
(PyObject *)NULL, (*pp_stack)-n, na,
|
(PyObject *)NULL, (*pp_stack)-n, na,
|
||||||
(*pp_stack)-2*nk, nk, d, nd, kwdefs,
|
(*pp_stack)-2*nk, nk, d, nd, kwdefs,
|
||||||
PyFunction_GET_CLOSURE(func));
|
PyFunction_GET_CLOSURE(func),
|
||||||
|
name, qualname);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
|
Loading…
Reference in New Issue