#35270: Optimize Model._meta._property_names
-------------------------------------+-------------------------------------
Reporter: Adam Johnson | Owner: Adam
Type: | Johnson
Cleanup/optimization | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Adam Johnson):
I didn’t fully explain the caching. What I meant by “per-class” is
per-*any*-class, not per *model class* - that means caching values for all
the property names in `object` (none), in `models.Model` (just `pk`, at
current), any mixing classes, and so on. Yes, the caching on `Options`
means it’s cached per *model class*, but just relying on that caching
means we don’t get to reuse the work done checking everything defined on
mixins, `Model`, or `object`.
`weak_key_cache` implements a pattern I’ve played with before to associate
extra data with an object without attaching arbitrary attributes, since
they might clash or affect operations like serialization or repr. django-
upgrade uses [
https://github.com/search?q=repo%3Aadamchainz%2Fdjango-
upgrade%20weakkeydictionary&type=code a bunch of WeakKeyDictionary
instances] to keep fixers modular to their own files.
It doesn’t make sense to use `@weak_key_cache` without the `__dict__`
optimization, because it requires one call per class, whilst the old
`dir()` approach checks attributes defined in the class *and* all
superclasses.
I profiled `__dict__` without `@weak_key_cache` though, using this
implementation:
{{{
@cached_property
def _property_names(self):
"""Return a set of the names of the properties defined on the
model."""
names = set()
for klass in self.model.__mro__:
names.update(
{
name
for name, value in klass.__dict__.items()
if isinstance(value, property)
}
)
return frozenset(names)
}}}
The result was that the calls took 2ms, keeping most of the savings. That
said, the project I’m using doesn’t have deep model inheritance or many
mixins, so we wouldn’t expect the caching to do so much.
If you’d both prefer this version, sure, we can go for it. Best to keep
things maintainable for all, and we can always add `@weak_key_cache` or
similar in the future.
--
Ticket URL: <
https://code.djangoproject.com/ticket/35270#comment:4>