-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Implement del object.__dict__
#5509
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
base: main
Are you sure you want to change the base?
Conversation
- Add support for deleting object dictionaries - Modify object_set_dict to handle dictionary deletion - Update setter value handling to support delete operations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
vm/src/object/core.rs
Outdated
@@ -717,6 +717,19 @@ impl PyObject { | |||
} | |||
} | |||
|
|||
pub fn delete_dict(&self) -> Result<(), ()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
set_dict in core.rs is only used for object_set_dict. Rather than adding delete_dict, I prefer to add this logic into set_dict
if other problems not found.
- Simplify object_set_dict method in object.rs - Update set_dict method in core.rs to handle both assignment and deletion - Consolidate dictionary modification logic
- Modify set_dict method to allow assigning dict to objects without existing dict - Update error handling in object_set_dict to provide more precise error message - Refactor type module to correctly handle dictionary assignment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this problem is more complex than I thought at first.
match obj { | ||
PySetterValue::Assign(obj) => T::try_from_object(vm, obj), | ||
PySetterValue::Delete => T::try_from_object(vm, vm.ctx.none()), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Affect multiple attribute in getset.rs
By this code change, the testLib/test/test_exceptions.py:620 testInvalidAttrs
fails
def testInvalidAttrs(self):
self.assertRaises(TypeError, setattr, Exception(), '__cause__', 1)
self.assertRaises(TypeError, delattr, Exception(), '__cause__')
self.assertRaises(TypeError, setattr, Exception(), '__context__', 1)
self.assertRaises(TypeError, delattr, Exception(), '__context__')
This is because FromPySetterValue trait affect not only dict but also other attributes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found only one way to restrict deletion of attribute except __dict__
by editing following part
// vm/src/builtins/getset.rs: 97
#[pyslot]
fn descr_set(
zelf: &PyObject,
obj: PyObjectRef,
value: PySetterValue<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<()> {
let zelf = zelf.try_to_ref::<Self>(vm)?;
if let Some(ref f) = zelf.setter {
match value {
PySetterValue::Assign(v) => f(vm, obj, PySetterValue::Assign(v)),
PySetterValue::Delete if zelf.name == "__dict__" => {
f(vm, obj, PySetterValue::Delete)
}
_ => Err(vm.new_type_error("can't delete attribute".to_owned())),
}
} else {
Err(vm.new_attribute_error(format!(
"attribute '{}' of '{}' objects is not writable",
zelf.name,
obj.class().name()
)))
}
}
@@ -1288,7 +1288,7 @@ fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) - | |||
descr_set(&descr, obj, PySetterValue::Assign(value), vm) | |||
} | |||
None => { | |||
object::object_set_dict(obj, value.try_into_value(vm)?, vm)?; | |||
object::object_set_dict(obj, PySetterValue::Assign(value.try_into_value(vm)?), vm)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Cannot resolve
Lib/test/test_descr.py:3399 test_set_dict
If I don't change code like this,object_set_dict
fails to be compiled because now value must be the typePySetterValue<PyDictRef>
.
There are two option for it, pass the valuePySetterValue::Assign
orPySetterValue::Delete
For the case of Assign I get
ERROR: test_set_dict (__main__.ClassPropertiesAndMethods.test_set_dict)
----------------------------------------------------------------------
Traceback (most recent call last):
File "Lib/test/test_descr.py", line 3415, in test_set_dict
del a.__dict__ # Deleting __dict__ is allowed
TypeError: Expected type 'dict' but 'NoneType' found.
For Delete case,
ERROR: test_set_dict (__main__.ClassPropertiesAndMethods.test_set_dict)
----------------------------------------------------------------------
Traceback (most recent call last):
File "Lib/test/test_descr.py", line 3404, in test_set_dict
self.assertEqual(a.b, 1)
AttributeError: 'C' object has no attribute 'b'
Test function
def test_set_dict(self):
# Testing __dict__ assignment...
class C(object): pass
a = C()
a.__dict__ = {'b': 1}
self.assertEqual(a.b, 1)
def cant(x, dict):
try:
x.__dict__ = dict
except (AttributeError, TypeError):
pass
else:
self.fail("shouldn't allow %r.__dict__ = %r" % (x, dict))
cant(a, None)
cant(a, [])
cant(a, 1)
del a.__dict__ # Deleting __dict__ is allowed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarified problem is this.
If I try to delete __dict__
, following methods are called.
- (getset.rs:164) set -
PySetterValue::Delete
becomesNone
impl<F, T, V, R> IntoPySetterFunc<(OwnedParam<T>, V, R, VirtualMachine)> for F
where
F: Fn(T, V, &VirtualMachine) -> R + 'static + Send + Sync,
T: TryFromObject,
V: FromPySetterValue,
R: IntoPyNoResult,
{
fn set(&self, obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
let obj = T::try_from_object(vm, obj)?;
let value = V::from_setter_value(vm, value)?; // None = V::from_setter_value(vm, PySetterValue::Delete)?;
(self)(obj, value, vm).into_noresult() // subtype_set_dict(obj, None, vm)
}
}
impl<T> FromPySetterValue for T
where
T: Sized + TryFromObject,
{
#[inline]
fn from_setter_value(vm: &VirtualMachine, obj: PySetterValue) -> PyResult<Self> {
match obj {
PySetterValue::Assign(obj) => T::try_from_object(vm, obj),
PySetterValue::Delete => T::try_from_object(vm, vm.ctx.none()),
}
}
}
- (type.rs:1275) subtype_set_dict - value should be PySetterValue again.
fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let cls = obj.class();
match find_base_dict_descr(cls, vm) {
Some(descr) => {
let descr_set = descr
.class()
.mro_find_map(|cls| cls.slots.descr_set.load())
.ok_or_else(|| {
vm.new_type_error(format!(
"this __dict__ descriptor does not support '{}' objects",
cls.name()
))
})?;
descr_set(&descr, obj, PySetterValue::Assign(value), vm)
}
None => {
object::object_set_dict(obj, PySetterValue::Assign(value.try_into_value(vm)?), vm)?;
Ok(())
}
}
}
Since the first V::from_setter_value(vm, value)
give same result for value = PySetterValue::Assign(None)
and value = PySetterValue::Delete
to None, This kind of error occurs.
This PR is based on the work by key262yek. The original PR is here: RustPython#5509
This PR is based on the work by key262yek. The original PR is here: RustPython#5509
This PR is based on the work by key262yek. The original PR is here: RustPython#5509
I add
object::delete_dict()
which changes Object's dict toNone
, and addPySetterValue::Delete
match case inobject::object_set_dict
.All things fine I think, but minor format issue remains.
In this code, since Err can be returned, we should define custom Error type.
I made several options for it.
Ok(())
when it does not have dict alreadydelete_dict
's parameter and return it.I cannot decide which one is better. Can you suggest about it?