-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
py/objtype: Add __dict__ attribute for class objects. #5324
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
Conversation
Would it be worth making the returned dict readonly (setting is_fixed on the underlying map, just to make it really clear about thr behaviour? |
That does sound sensible. The same change would make sense on the instance |
@jimmo at the definition of
https://github.com/micropython/micropython/blob/master/py/obj.h#L355 I'm not sure how firm that rule of needing to be ordered is, perhaps that flag should also be set in this case? |
c01b7b2
to
13a4ba7
Compare
@jimmo I've exposed I've not touched the |
@dpgeorge I believe this PR is ready for review, it behaves well for me at least. A couple of others on slack have asked about this functionality recently too! |
In any case a short comment in the code stating that a copy is returned meaning the original cannot be modified would be good, likewise for adding a test to the CPython difference documentation? And while we're at it maybe same for |
cb925cd
to
cdc47d8
Compare
cdc47d8
to
8c5dcda
Compare
8c5dcda
to
e45855a
Compare
I think this is a good change, it mirrors the availability of this on instances. But what I'd suggest to improve it is:
This should be a relatively small set of changes. Also a test would eventually be needed, including a cpydiff test :) |
I had originally thought to do this, however class if (attr == MP_QSTR___dict__) {
// Create a new dict with a copy of the instance's map items.
// This creates, unlike CPython, a 'read-only' __dict__: modifying
// it will not result in modifications to the actual instance members.
mp_obj_t members_dict = mp_obj_new_dict(0);
mp_obj_dict_t *dict = MP_OBJ_TO_PTR(members_dict);
dict->map = self->members;
dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(dict));
return;
} from
to
I also like your suggestions regarding the is_fixed flag too |
No need to use heap memory, instead: mp_obj_dict_t dict;
dict.map = self->members;
dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(&dict)); |
'mp_obj_dict_copy' starts with Also now with the next optimisation, in the case where the underlying instance map // Returns a read-only dict of the instance members.
// If the internal locals is not fixed, a copy will be created.
// This creates, unlike CPython, a 'read-only' __dict__: modifying
// it will not result in modifications to the actual instance members.
mp_obj_t members_dict = mp_obj_new_dict(0);
mp_obj_dict_t *dict = MP_OBJ_TO_PTR(members_dict);
dict->map = self->members;
if (dict->map.is_fixed) {
dest[0] = dict;
} else {
dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(dict));
dict = MP_OBJ_TO_PTR(dest[0]);
dict->map.is_fixed = 1;
}
return; |
e45855a
to
cb14655
Compare
That optimisation won't work for instances, just classes. |
cb14655
to
03ae3ec
Compare
Thanks for the suggestions and clarification, I've cleaned that up now and added a basic unit test. |
03ae3ec
to
ae7c0a2
Compare
dict.base.type = &mp_type_dict; | ||
dest[0] = mp_obj_dict_copy(MP_OBJ_FROM_PTR(&dict)); | ||
mp_obj_dict_t *dest_dict = MP_OBJ_TO_PTR(dest[0]); | ||
dest_dict->map.is_fixed = 1; |
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.
Can you please put these changes to the instance __dict__
lookup into a separate commit?
@stinos as the original author of this code, what are your thoughts on this change? In particular do you think it's a good idea to set is_fixed=1
to prevent modification of the returned dict (and so prevent subtle errors when the user tries to change the dict and expects those changes to be mirrored in the instance, which they are not)?
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.
Would be my preference to reduce chances of things being misused yes. And looking at the original discussion in #1757, I actually wanted it to be like that but that wasn't possible back then.
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.
Ok, so let's keep the is_fixed=1
part, to make the returned dict R/O. It will be a breaking change though, if users are relying on the dict being modifiable (eg because they know it's not reflected in the instance but they want anyway to reuse the dict for something else). But it'll fail pretty quickly now if someone is doing this and they can fix their code by simply doing d = dict(my_type.__dict__)
tests/basics/class_dict.py
Outdated
|
||
class Foo: | ||
self.a = 1 | ||
self.b = "bar" |
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.
self
shouldn't be needed here
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.
whoops, fixed along with splitting the commits.
ae7c0a2
to
b9614ec
Compare
py/objtype.c
Outdated
if (attr == MP_QSTR___dict__) { | ||
// Returns a read-only dict of the class attributes. | ||
// If the internal locals is not fixed, a copy will be created. | ||
mp_obj_dict_t *dict = MP_OBJ_FROM_PTR(self->locals_dict); |
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.
doesn't need a cast
py/objtype.c
Outdated
// If the internal locals is not fixed, a copy will be created. | ||
mp_obj_dict_t *dict = MP_OBJ_FROM_PTR(self->locals_dict); | ||
if (dict->map.is_fixed) { | ||
dest[0] = dict; |
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.
needs MP_OBJ_FROM_PTR
py/objtype.c
Outdated
if (dict->map.is_fixed) { | ||
dest[0] = dict; | ||
} else { | ||
dest[0] = mp_obj_dict_copy(dict); |
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.
needs MP_OBJ_FROM_PTR
b9614ec
to
bc310ef
Compare
remove lingering reference to settings.py in docs
The behavior mirrors instance object dict attribute where a copy of the local attributes are provided.
The function is only enabled if
MICROPY_CPYTHON_COMPAT
is set, the same as the instance version of this function.This can be useful to get access to the attributes on a class, eg. for #5322