-
-
Notifications
You must be signed in to change notification settings - Fork 8.3k
py/objtype: Add support for __set_name__. (no-self-modification version) #17693
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: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Eliminate the potential iterate-while-modifying hazard in the calls to user __set_name__ functions by marking the locals_dict as fixed during the __set_name__ loop. Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #17693 +/- ##
=======================================
Coverage 98.44% 98.44%
=======================================
Files 171 171
Lines 22192 22219 +27
=======================================
+ Hits 21847 21874 +27
Misses 345 345 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code size report:
|
I like this approach, it's nice and simple, and safe.
Do you think we'd be severely limiting this feature by disallowing locals dict updates? |
The use cases aren't overwhelming, so there's definitely merit to just disallowing locals dict updates; or maybe having a feature flag for this. Just to describe a few, though: Some descriptors need to coordinate with other descriptor instances on the same class (e.g. a serialization or ORM libraries); in these cases, it's common to have the first descriptor create an attribute on the class to hold a data structure that they all register in. (One of my own libraries use owner class attrs like this for the tables it uses to work out what registers are contiguous, for I2C transaction pooling.) class Registered:
def __set_name__(self, owner, name):
try:
owner.__registry[name] = self
except AttributeError:
owner.__registry = {name: self} In CPython, a library writer can also do this using a mixin class, with an It's also potentially a performance optimization in the case where a descriptor can replace itself with a concrete value at class-creation time, to avoid the indirection of dispatching access through a getter: class Configured:
def __set_name__(self, owner, name):
concrete_value = query_config_database(name)
setattr(owner, name, concrete_value)
class MyApp:
TIME_BETWEEN_WIDGETS: int = Configured() |
Summary
This PR implements the feature described in #15501, adding support for the
__set_name__
data model method.In particular, this implementation leverages the existing
is_fixed
flag bit on micropython's flag type to avoid the potential modify-while-iterating hazard in the simplest possible way, i.e. by preventing__set_name__
from modifying the owner class.Testing
This PR includes and passes the unit test originally submitted in #15500 to verify the feature's absence, as well as additional tests to specifically verify that attempting to mutate the descriptor's owner triggers an error.
Trade-offs and Alternatives
This is the least powerful of the four ways I've tried implementing this (just allowing the hazard in #15503, or copying a list of calls to make in #16806, or making a private copy of the dict in #16816), as the ability for a descriptor to add siblings or transparently delete or replace itself isn't an especially-rarely-used functionality in descriptor-heavy code.
The functionality in question can, though, for the most part be replaced by inheriting the owner class from a suitable parent class with an
__init_subclass__
method that handles this on behalf of its descriptors. This will need to be documented as a cpydiff workaround, though that can only really be considered a workaround once we have actual__init_subclass__
(#15511) or at least documented workarounds to that method's nonfunctionality (#16786).As an alternative, maybe this PR could be combined with either #16806 or #16816 with a feature flag that defaults to this minimal 'disallow modifying the owner' implementation, but can be set for platforms/applications where the full cost of making the extra copies is tolerable?