[Django] #31680: Preventing DeferredAttribute .__ get__ method unnecessary calls

32 views
Skip to first unread message

Django

unread,
Jun 8, 2020, 1:24:24 PM6/8/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute .__ get__ method unnecessary calls
-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: nobody
Type: | Status: new
Cleanup/optimization |
Component: Database | Version: 3.1
layer (models, ORM) |
Severity: Normal | Keywords:
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
To retrieve a deferred model attributes, the __get__ method is called
twice. This is because it uses the getattr() function, which in turn
causes the __get__ method to be called again.


{{{
def __get__(self, instance, cls=None):
"""
Retrieve and caches the value from the datastore on the first
lookup.
Return the cached value.
"""
if instance is None:
return self
data = instance.__dict__
field_name = self.field.attname
if field_name not in data:
# Let's see if the field is part of the parent chain. If so we
# might be able to reuse the already loaded value. Refs
#18343.
val = self._check_parent_chain(instance)
if val is None:
instance.refresh_from_db(fields=[field_name])
**val = getattr(instance, field_name)**
data[field_name] = val
return data[field_name]
}}}


To prevent this unnecessary call, we can simply extract the value from the
instance dict (at that moment it already contains the searched value):


{{{
def __get__(self, instance, cls=None):
"""
Retrieve and caches the value from the datastore on the first
lookup.
Return the cached value.
"""
if instance is None:
return self
data = instance.__dict__
field_name = self.field.attname
if field_name not in data:
# Let's see if the field is part of the parent chain. If so we
# might be able to reuse the already loaded value. Refs
#18343.
val = self._check_parent_chain(instance)
if val is None:
instance.refresh_from_db(fields=[field_name])
# Now the instance dict contains the reloaded data.
# Using getattr() will do extra call to __get__ method.
**val = data[field_name]**
data[field_name] = val
return data[field_name]
}}}

This reduces the number of method calls.

--
Ticket URL: <https://code.djangoproject.com/ticket/31680>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Jun 8, 2020, 1:28:28 PM6/8/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute .__ get__ method unnecessary calls
-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: nobody
Type: | Status: new
Cleanup/optimization |
Component: Database layer | Version: 3.1
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 1 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sultan):

* has_patch: 0 => 1
* easy: 0 => 1


--
Ticket URL: <https://code.djangoproject.com/ticket/31680#comment:1>

Django

unread,
Jun 8, 2020, 1:29:57 PM6/8/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute .__ get__ method unnecessary calls
-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: nobody
Type: | Status: new
Cleanup/optimization |
Component: Database layer | Version: 3.1
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 1 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Sultan:

Old description:

New description:

To retrieve a deferred model attributes, the __get__ method is called
twice. This is because it uses the getattr() function, which in turn
causes the __get__ method to be called again.


{{{
def __get__(self, instance, cls=None):
"""
Retrieve and caches the value from the datastore on the first
lookup.
Return the cached value.
"""
if instance is None:
return self
data = instance.__dict__
field_name = self.field.attname
if field_name not in data:
# Let's see if the field is part of the parent chain. If so we
# might be able to reuse the already loaded value. Refs
#18343.
val = self._check_parent_chain(instance)
if val is None:
instance.refresh_from_db(fields=[field_name])
**val = getattr(instance, field_name)**
data[field_name] = val
return data[field_name]
}}}


To prevent this unnecessary call, we can simply extract the value from the

instance dict (at that moment it already contains the reloaded value):


{{{
def __get__(self, instance, cls=None):
"""
Retrieve and caches the value from the datastore on the first
lookup.
Return the cached value.
"""
if instance is None:
return self
data = instance.__dict__
field_name = self.field.attname
if field_name not in data:
# Let's see if the field is part of the parent chain. If so we
# might be able to reuse the already loaded value. Refs
#18343.
val = self._check_parent_chain(instance)
if val is None:
instance.refresh_from_db(fields=[field_name])
# Now the instance dict contains the reloaded data.
# Using getattr() will do extra call to __get__ method.
**val = data[field_name]**
data[field_name] = val
return data[field_name]
}}}

This reduces the number of method calls.

--

--
Ticket URL: <https://code.djangoproject.com/ticket/31680#comment:2>

Django

unread,
Jun 10, 2020, 1:15:09 AM6/10/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute .__ get__ method unnecessary calls.
-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: Sultan
Type: | Status: assigned
Cleanup/optimization |
Component: Database layer | Version: master

(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 1 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by felixxm):

* owner: nobody => Sultan
* status: new => assigned
* version: 3.1 => master
* stage: Unreviewed => Accepted


Comment:

It looks that `val = getattr(instance, field_name)` is redundant.

--
Ticket URL: <https://code.djangoproject.com/ticket/31680#comment:3>

Django

unread,
Jun 10, 2020, 1:15:49 AM6/10/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute.__ get__() unnecessary calls.

-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: Sultan
Type: | Status: assigned
Cleanup/optimization |
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 1 | UI/UX: 0
-------------------------------------+-------------------------------------

--
Ticket URL: <https://code.djangoproject.com/ticket/31680#comment:4>

Django

unread,
Jun 10, 2020, 6:27:21 AM6/10/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute.__ get__() unnecessary calls.
-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: Sultan
Type: | Status: assigned
Cleanup/optimization |
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 1 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Sultan):

Replying to [comment:4 felixxm]: Yes you are right. I changed the code.
Now it has become much easier and better. Thank you.


{{{
def __get__(self, instance, cls=None):
"""
Retrieve and caches the value from the datastore on the first
lookup.
Return the cached value.
"""
if instance is None:
return self
data = instance.__dict__
field_name = self.field.attname
if field_name not in data:
# Let's see if the field is part of the parent chain. If so we
# might be able to reuse the already loaded value. Refs
#18343.
val = self._check_parent_chain(instance)
if val is None:
instance.refresh_from_db(fields=[field_name])

else:


data[field_name] = val
return data[field_name]
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/31680#comment:5>

Django

unread,
Jun 10, 2020, 6:55:46 AM6/10/20
to django-...@googlegroups.com
#31680: Preventing DeferredAttribute.__ get__() unnecessary calls.
-------------------------------------+-------------------------------------
Reporter: Sultan | Owner: Sultan
Type: | Status: closed

Cleanup/optimization |
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 1 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Mariusz Felisiak <felisiak.mariusz@…>):

* status: assigned => closed
* resolution: => fixed


Comment:

In [changeset:"678c8dfee458cda77fce0d1c127f1939dc134584" 678c8df]:
{{{
#!CommitTicketReference repository=""
revision="678c8dfee458cda77fce0d1c127f1939dc134584"
Fixed #31680 -- Removed unnecessary getattr() call in
DeferredAttribute.__get__().

refresh_from_db() loads fields values.
}}}

--
Ticket URL: <https://code.djangoproject.com/ticket/31680#comment:6>

Reply all
Reply to author
Forward
0 new messages