summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Chainz <me@adamj.eu>2016-12-15 18:42:44 +0000
committerTim Graham <timograham@gmail.com>2016-12-15 13:42:44 -0500
commitd2a26c1a90e837777dabdf3d67ceec4d2a70fb86 (patch)
treea3316323a8510f6c1296a6ed4f9481caf0b04125
parentf94475e5262abe78d9771bc353f741b20cc3c725 (diff)
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
-rw-r--r--django/db/models/base.py60
-rw-r--r--django/db/models/fields/__init__.py17
-rw-r--r--django/db/models/options.py11
3 files changed, 60 insertions, 28 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 6b4eb25009..53b761f45a 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -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
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index f1f68dbe7a..7434ac19ff 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -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
diff --git a/django/db/models/options.py b/django/db/models/options.py
index fd0b317865..335289beea 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -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)
+ })