-
-
Notifications
You must be signed in to change notification settings - Fork 374
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
__getattr__
in child class gets called when mixed with cached_property
#1288
Comments
I just checked against latest master and it still exists. It looks like 23.2.0 is when the error started, 23.1.0 works. |
@dlax any insights? 🤔 I can’t test anything for weeks to come. |
The fix from #1253 does not cover this inheritance case, indeed. I'll see if this can be fixed. |
This resolves the case where a sub-class of a slotted class defining some cached properties has a custom __getattr__() method. In that case, we need to build the custom __getattr__ implementation (see in _make_cached_property_getattr()) using cached properties from all classes in the MRO. In order to keep references of cached properties defined the inheritance hierarchy, we store them in a new __attrs_cached_properties__ attribute and finally build the "cached_properties" value, passed to _make_cached_property_getattr(), by combining current class' cached properties with that of all its parents. Also, when building __attrs_cached_properties__, we now clear current class' __dict__ (name 'cd'), thus saving an extra loop. Fix python-attrs#1288
This resolves the case where a sub-class of a slotted class defining some cached properties has a custom __getattr__() method. In that case, we need to build the custom __getattr__ implementation (see in _make_cached_property_getattr()) using cached properties from all classes in the MRO. In order to keep references of cached properties defined the inheritance hierarchy, we store them in a new __attrs_cached_properties__ attribute and finally build the "cached_properties" value, passed to _make_cached_property_getattr(), by combining current class' cached properties with that of all its parents. Also, when building __attrs_cached_properties__, we now clear current class' __dict__ (name 'cd'), thus saving an extra loop. Fix python-attrs#1288
This resolves the case where a sub-class of a slotted class defining some cached properties has a custom __getattr__() method. In that case, we need to build the custom __getattr__ implementation (see in _make_cached_property_getattr()) using cached properties from all classes in the MRO. In order to keep references of cached properties defined the inheritance hierarchy, we store them in a new __attrs_cached_properties__ attribute and finally build the "cached_properties" value, passed to _make_cached_property_getattr(), by combining current class' cached properties with that of all its parents. Also, when building __attrs_cached_properties__, we now clear current class' __dict__ (name 'cd'), thus saving an extra loop. Fix python-attrs#1288
#1289 fixes a slightly different version of the original problem, namely when the child class is also a (slotted) attr class, thus from the original example: @define
class Sup(Bob):
... I'm not sure if the original problem may be resolved. Or at least, not in the same manner as we have no mean to inspect the plain subclass. |
This resolves the case where a sub-class of a slotted class defining some cached properties has a custom __getattr__() method. In that case, we need to build the custom __getattr__ implementation (see in _make_cached_property_getattr()) using cached properties from all classes in the MRO. In order to keep references of cached properties defined the inheritance hierarchy, we store them in a new __attrs_cached_properties__ attribute and finally build the "cached_properties" value, passed to _make_cached_property_getattr(), by combining current class' cached properties with that of all its parents. Also, when building __attrs_cached_properties__, we now clear current class' __dict__ (name 'cd'), thus saving an extra loop. See python-attrs#1288
… with cached properties The __getattribute__() is documented as the "way to to actually get total control over attribute access", which is really what we want. Just changing that preserves the current behaviour, according to the test suite, but also makes sub-classing work better, e.g. when the subclass is not an attr-class and also defines a custom __getattr__() as evidenced in added test. Fix python-attrs#1288
… with cached properties The __getattribute__() is documented as the "way to to actually get total control over attribute access", which is really what we want. Just changing that preserves the current behaviour, according to the test suite, but also makes sub-classing work better, e.g. when the subclass is not an attr-class and also defines a custom __getattr__() as evidenced in added test. Fix python-attrs#1288
… with cached properties The __getattribute__() is documented as the "way to to actually get total control over attribute access", which is really what we want. Just changing that preserves the current behaviour, according to the test suite, but also makes sub-classing work better, e.g. when the subclass is not an attr-class and also defines a custom __getattr__() as evidenced in added test. Fix python-attrs#1288
#1291 is another attempt, which resolves both situations mentioned above. |
… with cached properties The __getattribute__() is documented as the "way to to actually get total control over attribute access", which is really what we want. Just changing that preserves the current behaviour, according to the test suite, but also makes sub-classing work better, e.g. when the subclass is not an attr-class and also defines a custom __getattr__() as evidenced in added test. Fix python-attrs#1288
Method `__getattribute__()` is documented as the "way to to actually get total control over attribute access" [1] so we change the implementation of slotted classes with cached properties by defining a `__getattribute__()` method instead of `__getattr__()` previously. [1]: https://docs.python.org/3/reference/datamodel.html#customizing-attribute-access Just changing that preserves the current behaviour, according to the test suite, but also makes sub-classing work better, e.g. when the subclass is not an attr-class and also defines a custom __getattr__() as evidenced in added test. In tests, we replace most custom `__getattr__()` definitions by equivalent `__getattribute__()` ones, except in regression tests where `__getattr__()` is explicitly involved. Also, in test_slots_with_multiple_cached_property_subclasses_works(), we replace the `if hasattr(super(), "__getattr__"):` by a `try:`/`except AttributeError:` as using `hasattr(..., "__getattribute__")` would be meaningless since `__getattribute__()` is always defined. Fix python-attrs#1288
Just noticed this issue, and thought I could give my insight for what it's worth. As noted the problem listed is unfixable with the approach, because
Runs with no errors (and prints 3). That said, obviously it's surprising to encounter this error, and it would be better if it could be avoided. IMHO, the It also comes at the significant (IMHO) cost of adding an overhead to every attribute access. That overhead might be relatively small, but given that both I think there's an option of adding a descriptor to the class for cached properties that wraps the slots descriptor, something like:
I also rejected this approach in favour of the I do think the performance characteristics are important to consider here, because adding an overhead to each attribute access arguably breaks the performance contract that |
Oof, so this is all a lot more complicated than hoped. I kinda agree @diabolo-dan here I think – if you're gonna inherit and overwrite That said, I suspect the descriptor-based approach's performance impact could be harmless enough to justify it (one usually doesn't use cached properties to save a local attribute lookup). However, unless I'm missing something, that would be a bit of magic we'd be adding, that could be confusing to our users's mental model (attrs tries to stay primarily out of the way). I fear we're on a trajectory of a whack-a-mole and increasingly adding more and more kludges. I've just added CodSpeed to attrs to make decisions like this easier, but I didn't get around writing really meaningful tests yet (I've mostly squatted at their EuroPython booth and spend most of the time getting it to work). @dlax do you habe thoughts? ISTM this is more of a documentation issue? Does the super approach from @diabolo-dan work for you? |
I missed the impact of using Having hacked significantly on this feature1, I also don't really feel comfortable with the implementation because it's fairly complex (sharing your "whack-a-mole" fear somehow). So I'm totally fine moving the resolution to documentation. Footnotes |
This previously did not error. I would expect
__getattr__
to not be called since the attribute exists.The text was updated successfully, but these errors were encountered: