Skip to content

gh-111696: type(str) returns the fully qualified name #112129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ Other Language Changes
equivalent of the :option:`-X frozen_modules <-X>` command-line option.
(Contributed by Yilei Yang in :gh:`111374`.)

* Formatting a type as a string now formats the type fully qualified name,
instead of formating its representation (`<class ...>`).
(Contributed by Victor Stinner in :gh:`111696`.)


New Modules
===========

Expand Down
2 changes: 1 addition & 1 deletion Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ def _get_field(cls, a_name, a_type, default_kw_only):
# indicator for mutability. Read the __hash__ attribute from the class,
# not the instance.
if f._field_type is _FIELD and f.default.__class__.__hash__ is None:
raise ValueError(f'mutable default {type(f.default)} for field '
raise ValueError(f'mutable default {type(f.default)!r} for field '
f'{f.name} is not allowed: use default_factory')

return f
Expand Down
2 changes: 1 addition & 1 deletion Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ def __call__(cls, value, names=None, *values, module=None, qualname=None, type=N
if names is None and type is None:
# no body? no data-type? possibly wrong usage
raise TypeError(
f"{cls} has no members; specify `names=()` if you meant to create a new, empty, enum"
f"{cls!r} has no members; specify `names=()` if you meant to create a new, empty, enum"
)
return cls._create_(
class_name=value,
Expand Down
2 changes: 1 addition & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ def _find_impl(cls, registry):
and match not in cls.__mro__
and not issubclass(match, t)):
raise RuntimeError("Ambiguous dispatch: {} or {}".format(
match, t))
repr(match), repr(t)))
break
if t in registry:
match = t
Expand Down
2 changes: 1 addition & 1 deletion Lib/idlelib/idle_test/test_searchbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def test_make_frame(self):
self.dialog.frame = Frame(self.root)
frame, label = self.dialog.make_frame()
self.assertEqual(label, '')
self.assertEqual(str(type(frame)), "<class 'tkinter.ttk.Frame'>")
self.assertEqual(repr(type(frame)), "<class 'tkinter.ttk.Frame'>")
# self.assertIsInstance(frame, Frame) fails when test is run by
# test_idle not run from IDLE editor. See issue 33987 PR.

Expand Down
2 changes: 1 addition & 1 deletion Lib/optparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ def _check_choice(self):
elif not isinstance(self.choices, (tuple, list)):
raise OptionError(
"choices must be a list of strings ('%s' supplied)"
% str(type(self.choices)).split("'")[1], self)
% repr(type(self.choices)).split("'")[1], self)
elif self.choices is not None:
raise OptionError(
"must not supply choices for type %r" % self.type, self)
Expand Down
2 changes: 1 addition & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1729,7 +1729,7 @@ def do_whatis(self, arg):
self.message('Class %s.%s' % (value.__module__, value.__qualname__))
return
# None of the above...
self.message(type(value))
self.message(repr(type(value)))

complete_whatis = _complete_expression

Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_cmd_line_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def _check_import_error(self, script_exec_args, expected_msg,
self.assertIn(expected_msg.encode('utf-8'), err)

def test_dash_c_loader(self):
rc, out, err = assert_python_ok("-c", "print(__loader__)")
rc, out, err = assert_python_ok("-c", "print(repr(__loader__))")
expected = repr(importlib.machinery.BuiltinImporter).encode("utf-8")
self.assertIn(expected, out)

Expand All @@ -163,7 +163,7 @@ def test_stdin_loader(self):
# stdin is an interactive tty.
p = spawn_python()
try:
p.stdin.write(b"print(__loader__)\n")
p.stdin.write(b"print(repr(__loader__))\n")
p.stdin.flush()
finally:
out = kill_python(p)
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,7 +738,7 @@ def test_disallowed_mutable_defaults(self):
with self.subTest(typ=typ):
# Can't use a zero-length value.
with self.assertRaisesRegex(ValueError,
f'mutable default {typ} for field '
f'mutable default {typ!r} for field '
'x is not allowed'):
@dataclass
class Point:
Expand All @@ -747,7 +747,7 @@ class Point:

# Nor a non-zero-length value
with self.assertRaisesRegex(ValueError,
f'mutable default {typ} for field '
f'mutable default {typ!r} for field '
'y is not allowed'):
@dataclass
class Point:
Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_descrtut.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ def merge(self, other):

Here's the new type at work:

>>> print(defaultdict) # show our type
>>> print(repr(defaultdict)) # show our type
<class 'test.test_descrtut.defaultdict'>
>>> print(type(defaultdict)) # its metatype
>>> print(repr(type(defaultdict))) # its metatype
<class 'type'>
>>> a = defaultdict(default=0.0) # create an instance
>>> print(a) # show the instance
{}
>>> print(type(a)) # show its type
>>> print(repr(type(a))) # show its type
<class 'test.test_descrtut.defaultdict'>
>>> print(a.__class__) # show its class
>>> print(repr(a.__class__)) # show its class
<class 'test.test_descrtut.defaultdict'>
>>> print(type(a) is a.__class__) # its type is its class
True
Expand Down Expand Up @@ -261,7 +261,7 @@ def merge(self, other):
>>> class C:
... @classmethod
... def foo(cls, y):
... print("classmethod", cls, y)
... print("classmethod", repr(cls), y)

>>> C.foo(1)
classmethod <class 'test.test_descrtut.C'> 1
Expand Down
6 changes: 3 additions & 3 deletions Lib/xmlrpc/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
encoding=self.encoding)
except BaseException as exc:
response = dumps(
Fault(1, "%s:%s" % (type(exc), exc)),
Fault(1, "%s:%s" % (repr(type(exc)), exc)),
encoding=self.encoding, allow_none=self.allow_none,
)

Expand Down Expand Up @@ -365,7 +365,7 @@ def system_multicall(self, call_list):
except BaseException as exc:
results.append(
{'faultCode' : 1,
'faultString' : "%s:%s" % (type(exc), exc)}
'faultString' : "%s:%s" % (repr(type(exc)), exc)}
)
return results

Expand Down Expand Up @@ -628,7 +628,7 @@ def _marshaled_dispatch(self, data, dispatch_method = None, path = None):
# (each dispatcher should have handled their own
# exceptions)
response = dumps(
Fault(1, "%s:%s" % (type(exc), exc)),
Fault(1, "%s:%s" % (repr(type(exc)), exc)),
encoding=self.encoding, allow_none=self.allow_none)
response = response.encode(self.encoding, 'xmlcharrefreplace')
return response
Expand Down
84 changes: 62 additions & 22 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1596,37 +1596,75 @@ static PyGetSetDef type_getsets[] = {
{0}
};

static PyObject *
type_repr(PyTypeObject *type)
static PyObject*
type_fullyqualname(PyTypeObject *type, int ignore_module_error)
{
// type is a static type and PyType_Ready() was not called on it yet?
if (type->tp_name == NULL) {
// type_repr() called before the type is fully initialized
// by PyType_Ready().
return PyUnicode_FromFormat("<class at %p>", type);
PyErr_SetString(PyExc_TypeError, "static type not initialized");
return NULL;
}

PyObject *mod, *name, *rtn;
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
// Static type
return PyUnicode_FromString(type->tp_name);
}

mod = type_module(type, NULL);
if (mod == NULL)
PyErr_Clear();
else if (!PyUnicode_Check(mod)) {
Py_SETREF(mod, NULL);
PyObject *qualname = type_qualname(type, NULL);
if (qualname == NULL) {
return NULL;
}
name = type_qualname(type, NULL);
if (name == NULL) {
Py_XDECREF(mod);

PyObject *module = type_module(type, NULL);
if (module == NULL) {
if (ignore_module_error) {
// type_repr() ignores type_module() errors
PyErr_Clear();
return qualname;
}

Py_DECREF(qualname);
return NULL;
}

if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins)))
rtn = PyUnicode_FromFormat("<class '%U.%U'>", mod, name);
else
rtn = PyUnicode_FromFormat("<class '%s'>", type->tp_name);
PyObject *result;
if (PyUnicode_Check(module)
&& !_PyUnicode_Equal(module, &_Py_ID(builtins)))
{
result = PyUnicode_FromFormat("%U.%U", module, qualname);
}
else {
result = Py_NewRef(qualname);
}
Py_DECREF(module);
Py_DECREF(qualname);
return result;
}

Py_XDECREF(mod);
Py_DECREF(name);
return rtn;

static PyObject *
type_str(PyTypeObject *type)
{
return type_fullyqualname(type, 0);
}


static PyObject *
type_repr(PyTypeObject *type)
{
if (type->tp_name == NULL) {
// If type_repr() is called before the type is fully initialized
// by PyType_Ready(), just format the type memory address.
return PyUnicode_FromFormat("<class at %p>", type);
}

PyObject *fullqualname = type_fullyqualname(type, 1);
if (fullqualname == NULL) {
return NULL;
}
PyObject *result = PyUnicode_FromFormat("<class '%U'>", fullqualname);
Py_DECREF(fullqualname);
return result;
}

static PyObject *
Expand Down Expand Up @@ -4540,6 +4578,7 @@ PyType_GetQualName(PyTypeObject *type)
return type_qualname(type, NULL);
}


void *
PyType_GetSlot(PyTypeObject *type, int slot)
{
Expand Down Expand Up @@ -5230,6 +5269,7 @@ type___sizeof___impl(PyTypeObject *self)
return PyLong_FromSize_t(size);
}


static PyMethodDef type_methods[] = {
TYPE_MRO_METHODDEF
TYPE___SUBCLASSES___METHODDEF
Expand Down Expand Up @@ -5354,7 +5394,7 @@ PyTypeObject PyType_Type = {
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)type_call, /* tp_call */
0, /* tp_str */
(reprfunc)type_str, /* tp_str */
(getattrofunc)_Py_type_getattro, /* tp_getattro */
(setattrofunc)type_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Expand Down