Skip to content
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
35 changes: 35 additions & 0 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4308,6 +4308,41 @@ def test_carloverre(self):
else:
self.fail("Carlo Verre __delattr__ succeeded!")

class A(type):
def __setattr__(cls, key, value):
type.__setattr__(cls, key, value)

class B:
pass

class C(B, A):
pass

obj = C('D', (object,), {})
try:
obj.test = True
except TypeError:
self.fail("assignment to classes that override __setattr__ should be legal")

class A(type):
def __setattr__(cls, key, value):
object.__setattr__(cls, key, value)

class B:
pass

class C(B, A):
pass

obj = C('D', (object,), {})
try:
obj.test = True
except TypeError:
pass
else:
self.fail("it should be illegal to use object.__setattr__ if type is closer in the MRO")


def test_weakref_segfault(self):
# Testing weakref segfault...
# SF 742911
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ Giovanni Cappellotto
Brett Cannon
Tristan Carel
Mike Carlton
David Caro
Pierre Carrier
Terry Carroll
Edward Catmur
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow overriding __setattr__ somewhere other than the next class in the MRO.
30 changes: 20 additions & 10 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5963,21 +5963,31 @@ hackcheck(PyObject *self, setattrofunc func, const char *what)
assert(PyTuple_Check(mro));
Py_ssize_t i, n;
n = PyTuple_GET_SIZE(mro);
int found_override = 0;
for (i = 0; i < n; i++) {
PyTypeObject *base = (PyTypeObject*) PyTuple_GET_ITEM(mro, i);
if (base->tp_setattro == func) {
/* 'func' is the earliest non-Python implementation in the MRO. */
if (base == &PyType_Type || base == &PyBaseObject_Type) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely not right. Special-casing types here (any type, really) will not help the general case.

if (base->tp_setattro == func) {
/* type or object are in the MRO and we are using it's func */
found_override = 1;
}
/* break to avoid going down the MRO, as we only care for the first
instance of type or object in the MRO */
break;
} else if (base->tp_setattro != slot_tp_setattro) {
/* 'base' is not a Python class and overrides 'func'.
Its tp_setattro should be called instead. */
PyErr_Format(PyExc_TypeError,
"can't apply this %s to %s object",
what,
type->tp_name);
return 0;
} else if (base->tp_setattro == func && base->tp_setattro == slot_tp_setattro) {
/* found a base class whose setattro matches the function being
called, that's allowed */
found_override = 1;
}
}
if (!found_override) {
PyErr_Format(PyExc_TypeError,
"can't apply this %s to %s object",
what,
type->tp_name);
return 0;
}

/* Either 'func' is not in the mro (which should fail when checking 'self'),
or it's the right slot function to call. */
return 1;
Expand Down