summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2014-06-16 13:59:28 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2014-06-16 13:59:28 (GMT)
commit40ee30181f7f7c71d6b49ceef35b5d3d20b88a6d (patch)
tree4f6813c9aff51c1f467504698456c7c4a1c33244
parent26171993fe629c614e62c7aef1196c0f53a30d85 (diff)
downloadcpython-40ee30181f7f7c71d6b49ceef35b5d3d20b88a6d.zip
cpython-40ee30181f7f7c71d6b49ceef35b5d3d20b88a6d.tar.gz
cpython-40ee30181f7f7c71d6b49ceef35b5d3d20b88a6d.tar.bz2
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.
-rw-r--r--Doc/library/inspect.rst14
-rw-r--r--Doc/whatsnew/3.5.rst6
-rw-r--r--Include/genobject.h8
-rw-r--r--Lib/test/test_generators.py39
-rw-r--r--Misc/NEWS6
-rw-r--r--Objects/genobject.c90
-rw-r--r--Python/ceval.c30
7 files changed, 170 insertions, 23 deletions
diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
index 21408f4..2830936 100644
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -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])
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
index b5536ad..17b703a 100644
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -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
--------------------
diff --git a/Include/genobject.h b/Include/genobject.h
index 65f1ecf..23571e6 100644
--- a/Include/genobject.h
+++ b/Include/genobject.h
@@ -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 *);
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index 91afe47..3882f4c 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -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:
diff --git a/Misc/NEWS b/Misc/NEWS
index 892ce76..9cf7065 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 08d30bf..831b15d 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -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 *op)
+{
+ Py_INCREF(op->gi_name);
+ return op->gi_name;
+}
+
+static int
+gen_set_name(PyGenObject *op, PyObject *value)
+{
+ PyObject *tmp;
+
+ /* 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_name(PyGenObject *gen)
+gen_get_qualname(PyGenObject *op)
{
- PyObject *name = ((PyCodeObject *)gen->gi_code)->co_name;
- Py_INCREF(name);
- return name;
+ Py_INCREF(op->gi_qualname);
+ return op->gi_qualname;
}
+static int
+gen_set_qualname(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.__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)
{
diff --git a/Python/ceval.c b/Python/ceval.c
index e14e772..3ede055 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -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 *