-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
gh-121141: add support for copy.replace
to AST nodes
#121162
Conversation
I think that it should only accept fields known to the AST class. |
I can live with that assumption but I need to update the docs then. However, what about constructions that add parent's back references. I would expect such nodes to be able to copy themselves but only change one single attribute (like a sibling) so that's why I only allowed the fields in By the way, is there something I should have taken care of because of singletons such as |
AFAIK, all other |
Actually, I assumed that AST nodes were somewhat similar to simple namespaces, which allows for arbitrary addition I think: cpython/Objects/namespaceobject.c Lines 227 to 232 in 6b280a8
But if you think we should restrict to fields known to the class, I can easily remove the code. |
I feel AST nodes are more like dataclasses. For dataclasses, unrecognized arguments are rejected by
|
Just after I sent my comment, I actually thought that maybe I should reject them since we anyway reject arbitrary keyword arguments in the constructor, maybe it's indeed closer to dataclasses. I convinced myself as well and since we reached a consensus, I'll remove this. I'll extend the |
|
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 think that the implementation can be simpler.
- Get fields (
PyObject_GetOptionalAttr((PyObject*)Py_TYPE(self), state->_fields, &fields)
). - Iterate them, and get corresponding values (
PyDict_GetItemRef(dict, name, &value)
). - Add corresponding name-value pair to the new dictionary.
- Update that dictionary with the keyword arguments dictionary.
- Create the object of the same type by passing a new dictionary as keyword arguments.
Validation is performed in the constructor.
Equivalent Python code:
def __replace__(self, /, **kwargs):
newkwargs = {}
for name in self._fields:
value = getattr(self, name)
newkwargs[name] = value
newkwargs.update(kwargs)
return type(self)(**kwargs)
Yes, that's what I decided after our discussion from yesterday. Currently the construction only warns about extra keyword arguments. For now, I'll leave that warning but this also means that the fields are replaced, even though the behaviour would disappear. In particular, in 3.15 and later, the tests should be changed, so I added some comments but is there a more rigourous way? |
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.
LGTM.
You even made the changes I thought about but didn't write so as not to be too picky. We think alike.
I added two commits in which the optional attributes are checked. I have other questions as well and I'm not sure if we should first patch them before continuing this.
|
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 think with this implementation we raise exceptions whenever it is needed (except at one point where a warning is emitted, but I think it's a separate issue; see my previous comment).
Anyway I hope that I have made the requested changes; please review again.
Ah the bot did not catch the fact that I said that I was done with the changes. I have made the requested changes; please review again. |
Thanks for making the requested changes! @serhiy-storchaka, @JelleZijlstra: please review the changes made to this pull request. |
🤖 New build scheduled with the buildbot fleet by @JelleZijlstra for commit bddcb97 🤖 If you want to schedule another build, you need to add the 🔨 test-with-refleak-buildbots label again. |
(I hope I didn't leak things. The previous implementation didn't but maybe this one does). I'll be leaving for today, my defense is tomorrow so I'll patch if afterwards if needed. |
Just running the refleak bots because it's good practice to do that when there's a big pile of new C code. Good luck with your defense! |
cc @JelleZijlstra @serhiy-storchaka
I eventually had enough time for this one, but I had a question on the implementation and efficiency. Assume that I have the following:
I cannot do
ast.Name(id='a', foo=...)
in 3.15 (it's deprecated) and this forbids me to call the constructor with the keyword arguments passed tocopy.replace
. So,copy.replace(node, id='b', foo=0)
withkwargs = {'id': '5', 'foo': 0}
is implemented as follows:node2 = ast.Name(id='b', ctx=node.ctx)
(this pops 'id' fromkwargs
, or more generally, all fields known to the AST class, but not those extra fields).node2.__dict__ |= kwargs
(i.e., you make the changes on the extra fields).node2.id == 'b'
andnode2.foo = 0
butnode2
has no attributebar
yet because the constructor did not carry this information.PyDict_Merge(node2.__dict__, node.__dict__, 0)
so that all remaining values fromnode.__dict__
that were neither obtained in 1 or 2 are not set.Ideally, I'd like to create a function that can add me whatever attributes I want without warning me but I'm not sure of the refactoring. So for now, I left that logic separate. There is a separate routine that checks whether you can replace a field or not (e.g.,
copy.replace(node, unknown=1)
would fail because the node has no attributes named unknown for now).I also fixed
ast.compare
because I may lack attributes or fields, and comparing two objects that lack attributes or fields would still be correct IMO. If you want me to make another PR for that, I can cherry pick the commit (it's the first one).EDIT: simple wording question but should it be "add support for [...] to AST nodes" or "for AST nodes"? (or whatever else?)
📚 Documentation preview 📚: https://cpython-previews--121162.org.readthedocs.build/