Skip to content
Open
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
2 changes: 1 addition & 1 deletion Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3953,7 +3953,7 @@ def __setattr__(self, name, value):
return object.__setattr__(self, name, value)
else:
raise AttributeError(
"'decimal.Context' object has no attribute '%s'" % name)
f"'decimal.Context' object has no attribute {name!r}")

def __delattr__(self, name):
raise AttributeError("%s cannot be deleted" % name)
Expand Down
2 changes: 1 addition & 1 deletion Lib/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __getattr__(name):
else:
return 2

raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
raise AttributeError(f"module '{__name__}' has no attribute {name!r}")


# Constants for months
Expand Down
10 changes: 4 additions & 6 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,8 @@ def __get__(self, instance, ownerclass=None):
if self.member is not None:
return self.member
else:
raise AttributeError(
'%r has no attribute %r' % (ownerclass, self.name)
)
errmsg = f'{ownerclass!r} has no attribute {self.name!r}'
raise AttributeError(errmsg)
if self.fget is not None:
# use previous enum.property
return self.fget(instance)
Expand All @@ -206,9 +205,8 @@ def __get__(self, instance, ownerclass=None):
try:
return ownerclass._member_map_[self.name]
except KeyError:
raise AttributeError(
'%r has no attribute %r' % (ownerclass, self.name)
) from None
errmsg = f'{ownerclass!r} has no attribute {self.name!r}'
raise AttributeError(errmsg) from None

def __set__(self, instance, value):
if self.fset is not None:
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,10 @@ def test_getattr(self):
self.assertRaises(AttributeError, getattr, sys, chr(sys.maxunicode))
# unicode surrogates are not encodable to the default encoding (utf8)
self.assertRaises(AttributeError, getattr, 1, "\uDAD1\uD51E")
with self.assertRaises(AttributeError) as cm:
getattr(object(), "a'b")
self.assertEqual(str(cm.exception),
''''object' object has no attribute "a'b"''')

def test_hasattr(self):
self.assertTrue(hasattr(sys, 'stdout'))
Expand Down Expand Up @@ -2155,6 +2159,15 @@ def test_setattr(self):
msg = r"^attribute name must be string, not 'int'$"
self.assertRaisesRegex(TypeError, msg, setattr, sys, 1, 'spam')

class A:
__slots__ = ()

with self.assertRaises(AttributeError) as cm:
setattr(object(), "a'b", 1)
self.assertEqual(str(cm.exception),
"""'object' object has no attribute "a'b" """
"and no __dict__ for setting new attributes")

# test_str(): see test_str.py and test_bytes.py for str() tests.

def test_sum(self):
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3695,6 +3695,19 @@ def test_to_integral_value(self):
self.assertRaises(TypeError, c.to_integral_value, '10')
self.assertRaises(TypeError, c.to_integral_value, 10, 'x')

def test_setattr(self):
Context = self.decimal.Context

ctx = Context()
ctx.prec = 123
with self.assertRaises(AttributeError):
ctx.xyz = 1
with self.assertRaises(AttributeError) as cm:
setattr(ctx, "a'b", 1)
self.assertEqual(str(cm.exception),
''''decimal.Context' object has no attribute "a'b"''')


@requires_cdecimal
class CContextAPItests(ContextAPItests, unittest.TestCase):
decimal = C
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_module/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ def test_missing_getattr(self):
self.assertRaisesRegex(
AttributeError, "module 'foo' has no attribute 'not_here'",
getattr, foo, "not_here")
self.assertRaisesRegex(
AttributeError, '''module 'foo' has no attribute "a'b"''',
getattr, foo, "a'b")

def test_no_docstring(self):
# Regularly initialized module, no docstring
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_unittest/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,10 @@ def testAssertHasAttr(self):
self.assertHasAttr(a, 'y')
self.assertEqual(str(cm.exception),
"'List' object has no attribute 'y'")
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(a, "a'b")
self.assertEqual(str(cm.exception),
''''List' object has no attribute "a'b"''')
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(List, 'spam')
self.assertEqual(str(cm.exception),
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_unittest/testmock/testmock.py
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,11 @@ def test_attributes(mock):
"Mock object has no attribute 'z'",
getattr, mock, 'z'
)
self.assertRaisesRegex(
AttributeError,
'''Mock object has no attribute "a'b"''',
getattr, mock, "a'b"
)
self.assertRaisesRegex(
AttributeError,
"Mock object has no attribute '__foobar__'",
Expand Down
6 changes: 3 additions & 3 deletions Lib/unittest/mock.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,13 +808,13 @@ def __setattr__(self, name, value):
elif (self._spec_set and self._mock_methods is not None and
name not in self._mock_methods and
name not in self.__dict__):
raise AttributeError("Mock object has no attribute '%s'" % name)
raise AttributeError(f"Mock object has no attribute {name!r}")
elif name in _unsupported_magics:
msg = 'Attempting to set unsupported magic method %r.' % name
msg = f'Attempting to set unsupported magic method {name!r}.'
raise AttributeError(msg)
elif name in _all_magics:
if self._mock_methods is not None and name not in self._mock_methods:
raise AttributeError("Mock object has no attribute '%s'" % name)
raise AttributeError(f"Mock object has no attribute {name!r}")

if not _is_instance_mock(value):
setattr(type(self), name, _get_method(name, value))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Format the attribute name with :func:`repr`, instead of :func:`str`, when
formatting :exc:`AttributeError` error message. For example,
``getattr(object(), "a'b")`` now formats the attribute error correctly.
Patch by Victor Stinner.
2 changes: 1 addition & 1 deletion Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -7497,7 +7497,7 @@ store_instance_attr_lock_held(PyObject *obj, PyDictValues *values,
PyObject *old_value = values->values[ix];
if (old_value == NULL && value == NULL) {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
Py_TYPE(obj)->tp_name, name);
(void)_PyObject_SetAttributeErrorContext(obj, name);
return -1;
Expand Down
2 changes: 1 addition & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1361,7 +1361,7 @@ descriptor_set_wrapped_attribute(PyObject *oobj, PyObject *name, PyObject *value
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Clear();
PyErr_Format(PyExc_AttributeError,
"'%.200s' object has no attribute '%U'",
"'%s' object has no attribute %R",
type_name, name);
Py_DECREF(dict);
return -1;
Expand Down
20 changes: 10 additions & 10 deletions Objects/moduleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1422,7 +1422,7 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
if (!mod_name || !PyUnicode_Check(mod_name)) {
Py_XDECREF(mod_name);
PyErr_Format(PyExc_AttributeError,
"module has no attribute '%U'", name);
"module has no attribute %R", name);
return NULL;
}
PyObject *spec;
Expand All @@ -1432,7 +1432,7 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
}
if (spec == NULL) {
PyErr_Format(PyExc_AttributeError,
"module '%U' has no attribute '%U'",
"module '%U' has no attribute %R",
mod_name, name);
Py_DECREF(mod_name);
return NULL;
Expand Down Expand Up @@ -1466,9 +1466,9 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
if (is_possibly_shadowing_stdlib) {
assert(origin);
PyErr_Format(PyExc_AttributeError,
"module '%U' has no attribute '%U' "
"(consider renaming '%U' since it has the same "
"name as the standard library module named '%U' "
"module %R has no attribute %R "
"(consider renaming %R since it has the same "
"name as the standard library module named %R "
"and prevents importing that standard library module)",
mod_name, name, origin, mod_name);
}
Expand All @@ -1483,22 +1483,22 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
// For non-stdlib modules, only mention the possibility of
// shadowing if the module is being initialized.
PyErr_Format(PyExc_AttributeError,
"module '%U' has no attribute '%U' "
"(consider renaming '%U' if it has the same name "
"module %R has no attribute %R "
"(consider renaming %R if it has the same name "
"as a library you intended to import)",
mod_name, name, origin);
}
else if (origin) {
PyErr_Format(PyExc_AttributeError,
"partially initialized "
"module '%U' from '%U' has no attribute '%U' "
"module %R from %R has no attribute %R "
"(most likely due to a circular import)",
mod_name, origin, name);
}
else {
PyErr_Format(PyExc_AttributeError,
"partially initialized "
"module '%U' has no attribute '%U' "
"module %R has no attribute %R "
"(most likely due to a circular import)",
mod_name, name);
}
Expand All @@ -1514,7 +1514,7 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
}
else if (rc == 0) {
PyErr_Format(PyExc_AttributeError,
"module '%U' has no attribute '%U'",
"module %R has no attribute %R",
mod_name, name);
}
}
Expand Down
20 changes: 10 additions & 10 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1330,7 +1330,7 @@ PyObject_GetAttr(PyObject *v, PyObject *name)
}
else {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);
}

Expand Down Expand Up @@ -1377,7 +1377,7 @@ _PyObject_GetAttrStackRef(PyObject *v, PyObject *name)
}
else {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);
}

Expand Down Expand Up @@ -1548,14 +1548,14 @@ PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value)
_PyObject_ASSERT(name, Py_REFCNT(name) >= 1);
if (tp->tp_getattr == NULL && tp->tp_getattro == NULL)
PyErr_Format(PyExc_TypeError,
"'%.100s' object has no attributes "
"'%s' object has no attributes "
"(%s .%U)",
tp->tp_name,
value==NULL ? "del" : "assign to",
name);
else
PyErr_Format(PyExc_TypeError,
"'%.100s' object has only read-only attributes "
"'%s' object has only read-only attributes "
"(%s .%U)",
tp->tp_name,
value==NULL ? "del" : "assign to",
Expand Down Expand Up @@ -1737,7 +1737,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
}

PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);

_PyObject_SetAttributeErrorContext(obj, name);
Expand Down Expand Up @@ -1871,7 +1871,7 @@ _PyObject_GetMethodStackRef(PyThreadState *ts, _PyStackRef *self,
}

PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);

_PyObject_SetAttributeErrorContext(obj, name);
Expand Down Expand Up @@ -1995,7 +1995,7 @@ _PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,

if (!suppress) {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);

_PyObject_SetAttributeErrorContext(obj, name);
Expand Down Expand Up @@ -2070,13 +2070,13 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
if (descr == NULL) {
if (tp->tp_setattro == PyObject_GenericSetAttr) {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U' and no "
"'%s' object has no attribute %R and no "
"__dict__ for setting new attributes",
tp->tp_name, name);
}
else {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);
}
_PyObject_SetAttributeErrorContext(obj, name);
Expand All @@ -2103,7 +2103,7 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name,
error_check:
if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
PyErr_Format(PyExc_AttributeError,
"'%.100s' object has no attribute '%U'",
"'%s' object has no attribute %R",
tp->tp_name, name);
_PyObject_SetAttributeErrorContext(obj, name);
}
Expand Down
4 changes: 2 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6513,7 +6513,7 @@ _Py_type_getattro_stackref(PyTypeObject *type, PyObject *name,
/* Give up */
if (suppress_missing_attribute == NULL) {
PyErr_Format(PyExc_AttributeError,
"type object '%.100s' has no attribute '%U'",
"type object '%s' has no attribute %R",
type->tp_name, name);
}
else {
Expand Down Expand Up @@ -6552,7 +6552,7 @@ type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,

if (_PyDict_SetItem_LockHeld(dict, name, value) < 0) {
PyErr_Format(PyExc_AttributeError,
"type object '%.50s' has no attribute '%U'",
"type object '%s' has no attribute %R",
((PyTypeObject*)type)->tp_name, name);
_PyObject_SetAttributeErrorContext((PyObject *)type, name);
return -1;
Expand Down
Loading