models: Memoize identical instances automatically
This combined with the previous changes has brought the memory use for a 14k track test-set down from about 75MB to 17MB or so. Note that this does however, mean that copy is now lying to us as it does not such thing whenever it can avoid it.
This commit is contained in:
parent
dd270ab87b
commit
b7375323e9
@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import copy
|
||||
import json
|
||||
import weakref
|
||||
|
||||
# TODO: split into base models, serialization and fields?
|
||||
|
||||
@ -24,7 +25,7 @@ class Field(object):
|
||||
"""
|
||||
|
||||
def __init__(self, default=None, type=None, choices=None):
|
||||
self._name = None # Set by FieldOwner
|
||||
self._name = None # Set by ImmutableObjectMeta
|
||||
self._choices = choices
|
||||
self._default = default
|
||||
self._type = type
|
||||
@ -130,7 +131,7 @@ class Collection(Field):
|
||||
return self._default.__class__(value) or None
|
||||
|
||||
|
||||
class FieldOwner(type):
|
||||
class ImmutableObjectMeta(type):
|
||||
|
||||
"""Helper to automatically assign field names to descriptors."""
|
||||
|
||||
@ -142,8 +143,20 @@ class FieldOwner(type):
|
||||
value._name = key
|
||||
|
||||
attrs['_fields'] = fields
|
||||
attrs['_instances'] = weakref.WeakValueDictionary()
|
||||
attrs['__slots__'] = ['_' + field for field in fields]
|
||||
return super(FieldOwner, cls).__new__(cls, name, bases, attrs)
|
||||
|
||||
for base in bases:
|
||||
if '__weakref__' in getattr(base, '__slots__', []):
|
||||
break
|
||||
else:
|
||||
attrs['__slots__'].append('__weakref__')
|
||||
|
||||
return super(ImmutableObjectMeta, cls).__new__(cls, name, bases, attrs)
|
||||
|
||||
def __call__(cls, *args, **kwargs): # noqa: N805
|
||||
instance = super(ImmutableObjectMeta, cls).__call__(*args, **kwargs)
|
||||
return cls._instances.setdefault(weakref.ref(instance), instance)
|
||||
|
||||
|
||||
class ImmutableObject(object):
|
||||
@ -157,7 +170,7 @@ class ImmutableObject(object):
|
||||
:type kwargs: any
|
||||
"""
|
||||
|
||||
__metaclass__ = FieldOwner
|
||||
__metaclass__ = ImmutableObjectMeta
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
@ -232,7 +245,7 @@ class ImmutableObject(object):
|
||||
raise TypeError(
|
||||
'copy() got an unexpected keyword argument "%s"' % key)
|
||||
super(ImmutableObject, other).__setattr__(key, value)
|
||||
return other
|
||||
return self._instances.setdefault(weakref.ref(other), other)
|
||||
|
||||
def serialize(self):
|
||||
data = {}
|
||||
|
||||
@ -9,7 +9,7 @@ def create_instance(field):
|
||||
"""Create an instance of a dummy class for testing fields."""
|
||||
|
||||
class Dummy(object):
|
||||
__metaclass__ = FieldOwner
|
||||
__metaclass__ = ImmutableObjectMeta
|
||||
attr = field
|
||||
|
||||
return Dummy()
|
||||
|
||||
@ -8,6 +8,22 @@ from mopidy.models import (
|
||||
TlTrack, Track, model_json_decoder)
|
||||
|
||||
|
||||
class CachingTest(unittest.TestCase):
|
||||
|
||||
def test_same_instance(self):
|
||||
self.assertIs(Track(), Track())
|
||||
|
||||
def test_same_instance_with_values(self):
|
||||
self.assertIs(Track(uri='test'), Track(uri='test'))
|
||||
|
||||
def test_different_instance_with_different_values(self):
|
||||
self.assertIsNot(Track(uri='test1'), Track(uri='test2'))
|
||||
|
||||
def test_different_instance_with_copy(self):
|
||||
t = Track(uri='test1')
|
||||
self.assertIsNot(t, t.copy(uri='test2'))
|
||||
|
||||
|
||||
class GenericCopyTest(unittest.TestCase):
|
||||
|
||||
def compare(self, orig, other):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user