diff --git a/tests/snippets/attr.py b/tests/snippets/attr.py index 98ed19cb40..65cfcd0643 100644 --- a/tests/snippets/attr.py +++ b/tests/snippets/attr.py @@ -4,12 +4,21 @@ class A: pass +class B: + x = 50 a = A() a.b = 10 assert hasattr(a, 'b') assert a.b == 10 +assert B.x == 50 + +# test delete class attribute with del keyword +del B.x +with assert_raises(AttributeError): + _ = B.x + # test override attribute setattr(a, 'b', 12) assert a.b == 12 diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index b026b0dd76..89a64198be 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -200,6 +200,23 @@ impl PyClassRef { Ok(()) } + fn del_attr(self, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + if let Some(attr) = class_get_attr(&self.class(), attr_name.as_str()) { + if let Some(ref descriptor) = class_get_attr(&attr.class(), "__delete__") { + return vm + .invoke(descriptor, vec![attr, self.into_object()]) + .map(|_| ()); + } + } + + if class_get_attr(&self, attr_name.as_str()).is_some() { + self.attributes.borrow_mut().remove(attr_name.as_str()); + Ok(()) + } else { + Err(vm.new_attribute_error(attr_name.as_str().to_string())) + } + } + // This is used for class initialisation where the vm is not yet available. pub fn set_str_attr>(&self, attr_name: &str, value: V) { self.attributes @@ -256,8 +273,8 @@ pub fn init(ctx: &PyContext) { "__prepare__" => ctx.new_rustfunc(PyClassRef::prepare), "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), "__setattr__" => ctx.new_rustfunc(PyClassRef::set_attr), + "__delattr__" => ctx.new_rustfunc(PyClassRef::del_attr), "__subclasses__" => ctx.new_rustfunc(PyClassRef::subclasses), - "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), "__instancecheck__" => ctx.new_rustfunc(PyClassRef::instance_check), "__subclasscheck__" => ctx.new_rustfunc(PyClassRef::subclass_check), "__doc__" => ctx.new_str(type_doc.to_string()),