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:
Victor Stinner 2014-06-16 15:59:28 +02:00
parent 26171993fe
commit 40ee30181f
7 changed files with 171 additions and 24 deletions

View File

@ -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])

View File

@ -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
-------------------- --------------------

View File

@ -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 *);

View File

@ -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:

View File

@ -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

View File

@ -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,33 +422,73 @@ 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},
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY}, {"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
@ -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)
{ {

View File

@ -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 *