Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

key262yek
Copy link
Contributor

I add object::delete_dict() which changes Object's dict to None, and add PySetterValue::Delete match case in object::object_set_dict.
All things fine I think, but minor format issue remains.

    pub fn delete_dict(&self) -> Result<(), ()> {
        match self.instance_dict() {
            Some(_) => {
                unsafe {
                    let ptr = self as *const _ as *mut PyObject;
                    (*ptr).0.dict = None;
                }
                Ok(())
            }
            None => Err(()),
        }
    }

In this code, since Err can be returned, we should define custom Error type.
I made several options for it.

  1. Return Ok(()) when it does not have dict already
  • Can it satisfy normal convention? or I should raise error when the object to be deleted does not exist?
  1. Make simple error type and return it.
  2. Return Option not Result
  3. Add something to delete_dict's parameter and return it.

I cannot decide which one is better. Can you suggest about it?

- Add support for deleting object dictionaries
- Modify object_set_dict to handle dictionary deletion
- Update setter value handling to support delete operations
Copy link
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

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

Thanks!

@@ -717,6 +717,19 @@ impl PyObject {
}
}

pub fn delete_dict(&self) -> Result<(), ()> {
Copy link
Member

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
Copy link
Contributor Author

@key262yek key262yek left a 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.

Comment on lines +39 to +42
match obj {
PySetterValue::Assign(obj) => T::try_from_object(vm, obj),
PySetterValue::Delete => T::try_from_object(vm, vm.ctx.none()),
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Affect multiple attribute in getset.rs
    By this code change, the test Lib/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.

Copy link
Contributor Author

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)?;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. 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 type PySetterValue<PyDictRef>.
    There are two option for it, pass the value PySetterValue::Assign or PySetterValue::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

Copy link
Contributor Author

@key262yek key262yek Feb 15, 2025

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.

  1. (getset.rs:164) set - PySetterValue::Delete becomes None
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()),
        }
    }
}
  1. (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.

hbina added a commit to hbina/RustPython that referenced this pull request Mar 2, 2025
This PR is based on the work by key262yek.
The original PR is here: RustPython#5509
hbina added a commit to hbina/RustPython that referenced this pull request Mar 8, 2025
This PR is based on the work by key262yek.
The original PR is here: RustPython#5509
hbina added a commit to hbina/RustPython that referenced this pull request Mar 23, 2025
This PR is based on the work by key262yek.
The original PR is here: RustPython#5509
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants