Skip to content

Support del __dict__#5378

Open
nielsbuwen wants to merge 4 commits into
RustPython:mainfrom
nielsbuwen:5355-support-del-dict
Open

Support del __dict__#5378
nielsbuwen wants to merge 4 commits into
RustPython:mainfrom
nielsbuwen:5355-support-del-dict

Conversation

@nielsbuwen
Copy link
Copy Markdown
Contributor

@nielsbuwen nielsbuwen commented Jul 29, 2024

I would like to take over this good first issue: #5355

It brings the interpreter in line with the cpython behaviour when deleting __dict__ attributes.

class Thing:
    def __init__(self):
        self.x = 1


t = Thing()

del t.__dict__  # no error

t.x  # this is now an AttributeError

Basically this replaces the instance dict with an empty dictionary. This seems to duplicate the cpython behaviour.

Things to improve

  • I'm not sure if there is a better place for the test
  • I don't like the let new_dict = match dict where i take the given dict or and empty one. I could add a unwrap_or_default for the PySetterValue? (found it)
  • I also don't like the let value = match value { where i convert the Assign from a PyObjectRef into a PyDictRef. But i could not find a map* variant that let's me pass a function that returns a PyResult (i guess i found it)

I'd love to hear your suggestions

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the correct directory. Putting this test under builtin_object.py is also fine.

@youknowone
Copy link
Copy Markdown
Member

youknowone commented Jul 29, 2024

The change looks good, but function type seems not allowing to do it.
Could you please also add this error handling?

======================================================================
FAIL: test_delete___dict__ (test.test_funcattrs.FunctionDictsTest.test_delete___dict__)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/work/RustPython/RustPython/pylib/Lib/test/test_funcattrs.py", line 360, in test_delete___dict__
    self.fail("deleting function dictionary should raise TypeError")
AssertionError: deleting function dictionary should raise TypeError

======================================================================
FAIL: test_setting_dict_to_invalid (test.test_funcattrs.FunctionDictsTest.test_setting_dict_to_invalid)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/runner/work/RustPython/RustPython/pylib/Lib/test/test_funcattrs.py", line 331, in test_setting_dict_to_invalid
    self.cannot_set_attr(self.b, '__dict__', None, TypeError)
  File "/home/runner/work/RustPython/RustPython/pylib/Lib/test/test_funcattrs.py", line 42, in cannot_set_attr
    self.fail("shouldn't be able to del %s" % name)
AssertionError: shouldn't be able to del __dict__

The related type is PyFunction.

Adding a getset is possible like:

// This impl must be one of `#[pyclass]` annotated
impl PyFunction {
    #[pygetset(setter)]
    fn __dict__(zelf: &Py<Self>, value: dict: PySetterValue<PyDictRef>,, vm: &VirtualMachine) -> ...
}

@nielsbuwen
Copy link
Copy Markdown
Contributor Author

I found out, how to delegate the __dict__ access to the underlying object. This brings a new problem:

foo.__dict__ contains the keys __doc__, __module__ and __annotations__ in RustPython, but not in CPython (there it's just an empty dict).

I guess this happens, because execute_make_function in frame.rs assigns these three, but they are not explicitly added as members in PyFunction.

Should i add these three to the PyFunction struct? And if yes, in another pull request?

@youknowone
Copy link
Copy Markdown
Member

Thank you for analysis. That's a good point. Yes, making them as its own getset totally makes sense. And yes, in separated PR would be better.

@nielsbuwen
Copy link
Copy Markdown
Contributor Author

I was able to remove __module__ and __annotations__ from the function __dict__, but the __doc__ does not go away, even if i implement a "pygetset" for it. The functions are not even getting called.

I declared them like this

#[pygetset(magic)]
fn doc(&self) -> PyObjectRef {...}

#[pygetset(magic, setter)]
fn set_doc(&self, value: PyObjectRef) {...}

So, something is intercepting the calls to my pygetset. Any idea what that might be? I looked at other pygetset for __doc__ but none of them is getting called either.

It also does not matter whether the function actually has a doc-string. If i comment out the part in compile.rs where it generates op codes to store the doc-string in the function then the doc-string of the function-type is used when writing foo.__doc__.

@youknowone
Copy link
Copy Markdown
Member

@nielsbuwen Making a PR of that changes will be helpful for me to understand what's happening. My first guess is execute_make_function in frame.rs

@nielsbuwen
Copy link
Copy Markdown
Contributor Author

@youknowone here you go: #5392

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