Ticket #16639: obj-creation-speed.diff

File obj-creation-speed.diff, 7.1 KB (added by akaariai, 4 years ago)
  • django/db/models/base.py

    diff --git a/django/db/models/base.py b/django/db/models/base.py
    index 71fd1f7..3e770fc 100644
    a b class Model(object): 
    277277    _deferred = False
    278278
    279279    def __init__(self, *args, **kwargs):
    280         signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
    281 
     280        if self._meta.has_pre_init_listeners or signals.pre_init.has_global_listeners:
     281            signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
    282282        # Set up the storage for instance state
    283283        self._state = ModelState()
    284284
    class Model(object): 
    367367            if kwargs:
    368368                raise TypeError("'%s' is an invalid keyword argument for this function" % kwargs.keys()[0])
    369369        super(Model, self).__init__()
    370         signals.post_init.send(sender=self.__class__, instance=self)
     370        if self._meta.has_post_init_listeners or signals.post_init.has_global_listeners:
     371            signals.post_init.send(sender=self.__class__, instance=self)
    371372
    372373    def __repr__(self):
    373374        try:
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    index 6f0f406..fc13cee 100644
    a b class Options(object): 
    5454        # from *other* models. Needed for some admin checks. Internal use only.
    5555        self.related_fkey_lookups = []
    5656
     57        # These variables are here to make it possible to skip pre_init
     58        # and post_init signals when there are no listeners for the model.
     59        # This can save up to 2/3 of the time used in Model __init__.
     60        # The variables are set by pre_init's and post_init's connect methods.
     61        self.has_pre_init_listeners = False
     62        self.has_post_init_listeners = False
     63
    5764    def contribute_to_class(self, cls, name):
    5865        from django.db import connection
    5966        from django.db.backends.util import truncate_name
  • django/db/models/signals.py

    diff --git a/django/db/models/signals.py b/django/db/models/signals.py
    index 48872e7..5b9065b 100644
    a b from django.dispatch import Signal 
    22
    33class_prepared = Signal(providing_args=["class"])
    44
    5 pre_init = Signal(providing_args=["instance", "args", "kwargs"])
    6 post_init = Signal(providing_args=["instance"])
     5# Pre and post init signals have subclasses with some optimizations. The subclass
     6# connect methods make sure that Model __init__ knows about somebody listening. We
     7# don't want to pay the overhead of sending these signals if nobody is listening, as
     8# model initialization is a common operation.
     9class PreInitSignal(Signal):
     10    has_global_listeners = False
     11    def connect(self, *args, **kwargs):
     12        if 'sender' in kwargs:
     13            kwargs['sender']._meta.has_pre_init_listeners = True
     14        else:
     15            self.has_global_listeners = True
     16        return super(PreInitSignal, self).connect(*args, **kwargs)
     17   
     18class PostInitSignal(Signal):
     19    has_global_listeners = False
     20    def connect(self, *args, **kwargs):
     21        if 'sender' in kwargs:
     22            kwargs['sender']._meta.has_post_init_listeners = True
     23        else:
     24            self.has_global_listeners = True
     25        return super(PostInitSignal, self).connect(*args, **kwargs)
     26
     27pre_init = PreInitSignal(providing_args=["instance", "args", "kwargs"])
     28post_init = PostInitSignal(providing_args=["instance"])
    729
    830pre_save = Signal(providing_args=["instance", "raw", "using"])
    931post_save = Signal(providing_args=["instance", "raw", "created", "using"])
  • tests/modeltests/signals/tests.py

    diff --git a/tests/modeltests/signals/tests.py b/tests/modeltests/signals/tests.py
    index 9b8bce0..5503786 100644
    a b class SignalTests(TestCase): 
    2929        # Save up the number of connected signals so that we can check at the
    3030        # end that all the signals we register get properly unregistered (#9989)
    3131        pre_signals = (
     32            len(signals.pre_init.receivers),
     33            len(signals.post_init.receivers),
    3234            len(signals.pre_save.receivers),
    3335            len(signals.post_save.receivers),
    3436            len(signals.pre_delete.receivers),
    class SignalTests(TestCase): 
    6769        def pre_save_decorator_sender_test(signal, sender, instance, **kwargs):
    6870            data.append(instance)
    6971
     72        def pre_init_test(signal, sender, **kwargs):
     73            data.append(sender)
     74        signals.pre_init.connect(pre_init_test, sender=Person)
     75
     76        def post_init_test(signal, sender, instance, **kwargs):
     77            data.append((instance, instance.pk is None))
     78        signals.post_init.connect(post_init_test, sender=Person)
     79
     80        def pre_init_all_models_test(signal, sender, **kwargs):
     81            data.append((sender, 'GLOBAL'))
     82        signals.pre_init.connect(pre_init_all_models_test)
     83
    7084        p1 = Person(first_name="John", last_name="Smith")
    71         self.assertEqual(data, [])
     85        self.assertEqual(data, [Person, (Person, 'GLOBAL'), (p1, True)])
     86        data[:] = []
    7287        p1.save()
    7388        self.assertEqual(data, [
    7489            (p1, False),
    class SignalTests(TestCase): 
    88103
    89104        # Car signal (sender defined)
    90105        c1 = Car(make="Volkswagon", model="Passat")
     106        self.assertEqual(data, [(Car, 'GLOBAL')])
     107        data[:] = []
    91108        c1.save()
    92109        self.assertEqual(data, [
    93110            (c1, False),
    class SignalTests(TestCase): 
    114131        data[:] = []
    115132
    116133        p2 = Person(first_name="James", last_name="Jones")
     134        self.assertEqual(data, [Person, (Person, 'GLOBAL'), (p2, True)])
     135        data[:] = []
    117136        p2.id = 99999
    118137        p2.save()
    119138        self.assertEqual(data, [
    class SignalTests(TestCase): 
    137156            (p2, False),
    138157            (p2, False)
    139158        ])
     159        data[:] = []
     160
     161        p3 = Person.objects.all()[0]
     162        self.assertEqual(data, [Person, (Person, 'GLOBAL'), (p3, False)])
     163        data = data[:]
    140164
    141165        self.assertQuerysetEqual(
    142166            Person.objects.all(), [
    class SignalTests(TestCase): 
    145169            unicode
    146170        )
    147171
     172        signals.pre_init.disconnect(pre_init_test, sender=Person)
     173        signals.pre_init.disconnect(pre_init_all_models_test)
     174        # We need to do a dirty trick here - connecting to a global pre or post init
     175        # signal sets a state for the whole Django project - we want to get rid of
     176        # that state.
     177        signals.pre_init.has_global_listeners = False
     178        signals.post_init.disconnect(post_init_test, sender=Person)
    148179        signals.post_delete.disconnect(post_delete_test)
    149180        signals.pre_delete.disconnect(pre_delete_test)
    150181        signals.post_save.disconnect(post_save_test)
    class SignalTests(TestCase): 
    154185
    155186        # Check that all our signals got disconnected properly.
    156187        post_signals = (
     188            len(signals.pre_init.receivers),
     189            len(signals.post_init.receivers),
    157190            len(signals.pre_save.receivers),
    158191            len(signals.post_save.receivers),
    159192            len(signals.pre_delete.receivers),
Back to Top