1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Optimized Model instantiation a bit.

* Avoid some unnecessary attribute lookups, e.g. access signals directly rather than from module
* Alias some repeat accesses inside the method to use the slightly faster local lookups
* Use tuple to iterate remaining kwargs as it's faster to construct
* Cache Field.get_default() to avoid running through all the logic on every call
* Use a cached list of the properties on the model class to avoid repeat isinstance() calls
This commit is contained in:
Adam Chainz 2016-12-15 18:42:44 +00:00 committed by Tim Graham
parent f94475e526
commit d2a26c1a90
3 changed files with 60 additions and 28 deletions

View File

@ -16,7 +16,6 @@ from django.db import (
DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY, DatabaseError, connection,
connections, router, transaction,
)
from django.db.models import signals
from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import CASCADE, Collector
from django.db.models.fields.related import (
@ -25,6 +24,9 @@ from django.db.models.fields.related import (
from django.db.models.manager import Manager
from django.db.models.options import Options
from django.db.models.query import Q
from django.db.models.signals import (
class_prepared, post_init, post_save, pre_init, pre_save,
)
from django.db.models.utils import make_model_tuple
from django.utils import six
from django.utils.deprecation import RemovedInDjango20Warning
@ -366,7 +368,7 @@ class ModelBase(type):
manager.auto_created = True
cls.add_to_class('objects', manager)
signals.class_prepared.send(sender=cls)
class_prepared.send(sender=cls)
def _requires_legacy_default_manager(cls): # RemovedInDjango20Warning
opts = cls._meta
@ -465,7 +467,13 @@ class ModelState(object):
class Model(six.with_metaclass(ModelBase)):
def __init__(self, *args, **kwargs):
signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
# Alias some things as locals to avoid repeat global lookups
cls = self.__class__
opts = self._meta
_setattr = setattr
_DEFERRED = DEFERRED
pre_init.send(sender=cls, args=args, kwargs=kwargs)
# Set up the storage for instance state
self._state = ModelState()
@ -474,27 +482,27 @@ class Model(six.with_metaclass(ModelBase)):
# overrides it. It should be one or the other; don't duplicate the work
# The reason for the kwargs check is that standard iterator passes in by
# args, and instantiation for iteration is 33% faster.
if len(args) > len(self._meta.concrete_fields):
if len(args) > len(opts.concrete_fields):
# Daft, but matches old exception sans the err msg.
raise IndexError("Number of args exceeds number of fields")
if not kwargs:
fields_iter = iter(self._meta.concrete_fields)
fields_iter = iter(opts.concrete_fields)
# The ordering of the zip calls matter - zip throws StopIteration
# when an iter throws it. So if the first iter throws it, the second
# is *not* consumed. We rely on this, so don't change the order
# without changing the logic.
for val, field in zip(args, fields_iter):
if val is DEFERRED:
if val is _DEFERRED:
continue
setattr(self, field.attname, val)
_setattr(self, field.attname, val)
else:
# Slower, kwargs-ready version.
fields_iter = iter(self._meta.fields)
fields_iter = iter(opts.fields)
for val, field in zip(args, fields_iter):
if val is DEFERRED:
if val is _DEFERRED:
continue
setattr(self, field.attname, val)
_setattr(self, field.attname, val)
kwargs.pop(field.name, None)
# Now we're left with the unprocessed fields that *must* come from
@ -539,28 +547,28 @@ class Model(six.with_metaclass(ModelBase)):
# field.name instead of field.attname (e.g. "user" instead of
# "user_id") so that the object gets properly cached (and type
# checked) by the RelatedObjectDescriptor.
if rel_obj is not DEFERRED:
setattr(self, field.name, rel_obj)
if rel_obj is not _DEFERRED:
_setattr(self, field.name, rel_obj)
else:
if val is not DEFERRED:
setattr(self, field.attname, val)
if val is not _DEFERRED:
_setattr(self, field.attname, val)
if kwargs:
for prop in list(kwargs):
property_names = opts._property_names
for prop in tuple(kwargs):
try:
# Any remaining kwargs must correspond to properties or
# virtual fields.
if (isinstance(getattr(self.__class__, prop), property) or
self._meta.get_field(prop)):
if kwargs[prop] is not DEFERRED:
setattr(self, prop, kwargs[prop])
if prop in property_names or opts.get_field(prop):
if kwargs[prop] is not _DEFERRED:
_setattr(self, prop, kwargs[prop])
del kwargs[prop]
except (AttributeError, FieldDoesNotExist):
pass
if kwargs:
raise TypeError("'%s' is an invalid keyword argument for this function" % list(kwargs)[0])
super(Model, self).__init__()
signals.post_init.send(sender=self.__class__, instance=self)
post_init.send(sender=cls, instance=self)
@classmethod
def from_db(cls, db, field_names, values):
@ -816,8 +824,10 @@ class Model(six.with_metaclass(ModelBase)):
cls = cls._meta.concrete_model
meta = cls._meta
if not meta.auto_created:
signals.pre_save.send(sender=origin, instance=self, raw=raw, using=using,
update_fields=update_fields)
pre_save.send(
sender=origin, instance=self, raw=raw, using=using,
update_fields=update_fields,
)
with transaction.atomic(using=using, savepoint=False):
if not raw:
self._save_parents(cls, using, update_fields)
@ -829,8 +839,10 @@ class Model(six.with_metaclass(ModelBase)):
# Signal that the save is complete
if not meta.auto_created:
signals.post_save.send(sender=origin, instance=self, created=(not updated),
update_fields=update_fields, raw=raw, using=using)
post_save.send(
sender=origin, instance=self, created=(not updated),
update_fields=update_fields, raw=raw, using=using,
)
save_base.alters_data = True

View File

@ -92,6 +92,10 @@ def _empty(of_cls):
return new
def return_None():
return None
@total_ordering
@python_2_unicode_compatible
class Field(RegisterLookupMixin):
@ -771,13 +775,18 @@ class Field(RegisterLookupMixin):
"""
Returns the default value for this field.
"""
return self._get_default()
@cached_property
def _get_default(self):
if self.has_default():
if callable(self.default):
return self.default()
return self.default
return self.default
return lambda: self.default
if not self.empty_strings_allowed or self.null and not connection.features.interprets_empty_strings_as_nulls:
return None
return ""
return return_None
return six.text_type # returns empty string
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_choices_to=None):
"""Returns choices with a default blank choices included, for use

View File

@ -883,3 +883,14 @@ class Options(object):
@has_auto_field.setter
def has_auto_field(self, value):
pass
@cached_property
def _property_names(self):
"""
Return a set of the names of the properties defined on the model.
Internal helper for model initialization.
"""
return frozenset({
attr for attr in
dir(self.model) if isinstance(getattr(self.model, attr), property)
})