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 |
|
||||
| | | variables |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| generator | __name__ | name |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| | __qualname__ | qualified name |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| | gi_frame | frame |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| | gi_running | is the generator running? |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| | gi_code | code |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| builtin | __doc__ | documentation string |
|
||||
+-----------+-----------------+---------------------------+
|
||||
| | __name__ | original name of this |
|
||||
|
@ -169,6 +179,10 @@ attributes:
|
|||
| | | ``None`` |
|
||||
+-----------+-----------------+---------------------------+
|
||||
|
||||
.. versionchanged:: 3.5
|
||||
|
||||
Add ``__qualname__`` attribute to generators.
|
||||
|
||||
|
||||
.. 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
|
||||
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
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -25,6 +25,12 @@ typedef struct {
|
|||
|
||||
/* List of weak reference. */
|
||||
PyObject *gi_weakreflist;
|
||||
|
||||
/* Name of the generator. */
|
||||
PyObject *gi_name;
|
||||
|
||||
/* Qualified name of the generator. */
|
||||
PyObject *gi_qualname;
|
||||
} PyGenObject;
|
||||
|
||||
PyAPI_DATA(PyTypeObject) PyGen_Type;
|
||||
|
@ -33,6 +39,8 @@ PyAPI_DATA(PyTypeObject) PyGen_Type;
|
|||
#define PyGen_CheckExact(op) (Py_TYPE(op) == &PyGen_Type)
|
||||
|
||||
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_FetchStopIterationValue(PyObject **);
|
||||
PyObject *_PyGen_Send(PyGenObject *, PyObject *);
|
||||
|
|
|
@ -50,6 +50,45 @@ class FinalizationTest(unittest.TestCase):
|
|||
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 = """
|
||||
Let's try a simple generator:
|
||||
|
||||
|
|
|
@ -10,6 +10,12 @@ Release date: TBA
|
|||
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
|
||||
parser now attempts to generate more meaningful (or at least more search
|
||||
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(gen->gi_code);
|
||||
Py_VISIT(gen->gi_name);
|
||||
Py_VISIT(gen->gi_qualname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -58,6 +60,8 @@ gen_dealloc(PyGenObject *gen)
|
|||
_PyObject_GC_UNTRACK(self);
|
||||
Py_CLEAR(gen->gi_frame);
|
||||
Py_CLEAR(gen->gi_code);
|
||||
Py_CLEAR(gen->gi_name);
|
||||
Py_CLEAR(gen->gi_qualname);
|
||||
PyObject_GC_Del(gen);
|
||||
}
|
||||
|
||||
|
@ -418,33 +422,73 @@ static PyObject *
|
|||
gen_repr(PyGenObject *gen)
|
||||
{
|
||||
return PyUnicode_FromFormat("<generator object %S at %p>",
|
||||
((PyCodeObject *)gen->gi_code)->co_name,
|
||||
gen);
|
||||
gen->gi_qualname, gen);
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
gen_get_name(PyGenObject *gen)
|
||||
gen_get_name(PyGenObject *op)
|
||||
{
|
||||
PyObject *name = ((PyCodeObject *)gen->gi_code)->co_name;
|
||||
Py_INCREF(name);
|
||||
return name;
|
||||
Py_INCREF(op->gi_name);
|
||||
return op->gi_name;
|
||||
}
|
||||
|
||||
static int
|
||||
gen_set_name(PyGenObject *op, PyObject *value)
|
||||
{
|
||||
PyObject *tmp;
|
||||
|
||||
PyDoc_STRVAR(gen__name__doc__,
|
||||
"Return the name of the generator's associated code object.");
|
||||
/* Not legal to del gen.gi_name or to set it to anything
|
||||
* 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[] = {
|
||||
{"__name__", (getter)gen_get_name, NULL, gen__name__doc__},
|
||||
{NULL}
|
||||
{"__name__", (getter)gen_get_name, (setter)gen_set_name,
|
||||
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[] = {
|
||||
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY},
|
||||
{"gi_running", T_BOOL, offsetof(PyGenObject, gi_running), READONLY},
|
||||
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY},
|
||||
{"gi_frame", T_OBJECT, offsetof(PyGenObject, gi_frame), READONLY},
|
||||
{"gi_running", T_BOOL, offsetof(PyGenObject, gi_running), READONLY},
|
||||
{"gi_code", T_OBJECT, offsetof(PyGenObject, gi_code), READONLY},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
@ -510,7 +554,7 @@ PyTypeObject PyGen_Type = {
|
|||
};
|
||||
|
||||
PyObject *
|
||||
PyGen_New(PyFrameObject *f)
|
||||
PyGen_NewWithQualName(PyFrameObject *f, PyObject *name, PyObject *qualname)
|
||||
{
|
||||
PyGenObject *gen = PyObject_GC_New(PyGenObject, &PyGen_Type);
|
||||
if (gen == NULL) {
|
||||
|
@ -523,10 +567,26 @@ PyGen_New(PyFrameObject *f)
|
|||
gen->gi_code = (PyObject *)(f->f_code);
|
||||
gen->gi_running = 0;
|
||||
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);
|
||||
return (PyObject *)gen;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
PyGen_New(PyFrameObject *f)
|
||||
{
|
||||
return PyGen_NewWithQualName(f, NULL, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
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
|
||||
the test in the if statements in Misc/gdbinit (pystack and pystackv). */
|
||||
|
||||
PyObject *
|
||||
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
|
||||
static PyObject *
|
||||
_PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
|
||||
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;
|
||||
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
|
||||
* and return that as the value. */
|
||||
return PyGen_New(f);
|
||||
return PyGen_NewWithQualName(f, name, qualname);
|
||||
}
|
||||
|
||||
retval = PyEval_EvalFrameEx(f,0);
|
||||
|
@ -3615,6 +3616,16 @@ fail: /* Jump here from prelude on failure */
|
|||
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 *
|
||||
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 *argdefs = PyFunction_GET_DEFAULTS(func);
|
||||
PyObject *kwdefs = PyFunction_GET_KW_DEFAULTS(func);
|
||||
PyObject *name = ((PyFunctionObject *)func) -> func_name;
|
||||
PyObject *qualname = ((PyFunctionObject *)func) -> func_qualname;
|
||||
PyObject **d = NULL;
|
||||
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);
|
||||
nd = Py_SIZE(argdefs);
|
||||
}
|
||||
return PyEval_EvalCodeEx((PyObject*)co, globals,
|
||||
(PyObject *)NULL, (*pp_stack)-n, na,
|
||||
(*pp_stack)-2*nk, nk, d, nd, kwdefs,
|
||||
PyFunction_GET_CLOSURE(func));
|
||||
return _PyEval_EvalCodeWithName((PyObject*)co, globals,
|
||||
(PyObject *)NULL, (*pp_stack)-n, na,
|
||||
(*pp_stack)-2*nk, nk, d, nd, kwdefs,
|
||||
PyFunction_GET_CLOSURE(func),
|
||||
name, qualname);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
|
Loading…
Reference in New Issue