models: Switch to slots to reduce memory usage per instance

This commit is contained in:
Thomas Adamcik 2015-04-08 00:30:53 +02:00
parent 08fd99ffdb
commit 0fee1b4b11
3 changed files with 28 additions and 20 deletions

View File

@ -45,7 +45,7 @@ class Field(object):
def __get__(self, instance, owner):
if not instance:
return self
return instance.__dict__.get(self._name, self._default)
return getattr(instance, '_' + self._name, self._default)
def __set__(self, instance, value):
if value is not None:
@ -54,10 +54,11 @@ class Field(object):
if value is None or value == self._default:
self.__delete__(instance)
else:
instance.__dict__[self._name] = value
setattr(instance, '_' + self._name, value)
def __delete__(self, instance):
instance.__dict__.pop(self._name, None)
if hasattr(instance, '_' + self._name):
delattr(instance, '_' + self._name)
class String(Field):
@ -134,12 +135,14 @@ class FieldOwner(type):
"""Helper to automatically assign field names to descriptors."""
def __new__(cls, name, bases, attrs):
attrs['_fields'] = []
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
attrs['_fields'].append(key)
fields[key] = '_' + key
value._name = key
attrs['_fields'].sort()
attrs['_fields'] = fields
attrs['__slots__'] = ['_' + field for field in fields]
return super(FieldOwner, cls).__new__(cls, name, bases, attrs)
@ -165,14 +168,23 @@ class ImmutableObject(object):
super(ImmutableObject, self).__setattr__(key, value)
def __setattr__(self, name, value):
if name in self.__slots__:
return super(ImmutableObject, self).__setattr__(name, value)
raise AttributeError('Object is immutable.')
def __delattr__(self, name):
if name in self.__slots__:
return super(ImmutableObject, self).__delattr__(name)
raise AttributeError('Object is immutable.')
def _items(self):
for field, key in self._fields.items():
if hasattr(self, key):
yield field, getattr(self, key)
def __repr__(self):
kwarg_pairs = []
for (key, value) in sorted(self.__dict__.items()):
for key, value in sorted(self._items()):
if isinstance(value, (frozenset, tuple)):
if not value:
continue
@ -185,15 +197,14 @@ class ImmutableObject(object):
def __hash__(self):
hash_sum = 0
for key, value in self.__dict__.items():
for key, value in self._items():
hash_sum += hash(key) + hash(value)
return hash_sum
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.__dict__ == other.__dict__
return dict(self._items()) == dict(other._items())
def __ne__(self, other):
return not self.__eq__(other)
@ -224,7 +235,7 @@ class ImmutableObject(object):
def serialize(self):
data = {}
data['__model__'] = self.__class__.__name__
for key, value in self.__dict__.items():
for key, value in self._items():
if isinstance(value, (set, frozenset, list, tuple)):
value = [
v.serialize() if isinstance(v, ImmutableObject) else v

View File

@ -29,15 +29,14 @@ class FieldDescriptorTest(unittest.TestCase):
instance = create_instance(Field())
self.assertIsNone(instance.attr)
def test_field_does_not_store_default_in_dict(self):
def test_field_does_not_store_default(self):
instance = create_instance(Field())
self.assertNotIn('attr', instance.__dict__)
self.assertFalse(hasattr(instance, '_attr'))
def test_field_assigment_and_retrival(self):
instance = create_instance(Field())
instance.attr = 1234
self.assertEqual(1234, instance.attr)
self.assertEqual(1234, instance.__dict__['attr'])
def test_field_can_be_reassigned(self):
instance = create_instance(Field())
@ -50,14 +49,14 @@ class FieldDescriptorTest(unittest.TestCase):
instance.attr = 1234
del instance.attr
self.assertEqual(None, instance.attr)
self.assertNotIn('attr', instance.__dict__)
self.assertFalse(hasattr(instance, '_attr'))
def test_field_can_be_set_to_none(self):
instance = create_instance(Field())
instance.attr = 1234
instance.attr = None
self.assertEqual(None, instance.attr)
self.assertNotIn('attr', instance.__dict__)
self.assertFalse(hasattr(instance, '_attr'))
def test_field_can_be_set_default(self):
default = object()
@ -65,7 +64,7 @@ class FieldDescriptorTest(unittest.TestCase):
instance.attr = 1234
instance.attr = default
self.assertEqual(default, instance.attr)
self.assertNotIn('attr', instance.__dict__)
self.assertFalse(hasattr(instance, '_attr'))
class FieldTest(unittest.TestCase):
@ -183,13 +182,11 @@ class CollectionTest(unittest.TestCase):
instance = create_instance(Collection(type=int, container=frozenset))
instance.attr = []
self.assertEqual(frozenset(), instance.attr)
self.assertNotIn('attr', instance.__dict__)
def test_collection_gets_stored_in_container(self):
instance = create_instance(Collection(type=int, container=frozenset))
instance.attr = [1, 2, 3]
self.assertEqual(frozenset([1, 2, 3]), instance.attr)
self.assertEqual(frozenset([1, 2, 3]), instance.__dict__['attr'])
def test_collection_with_wrong_type(self):
instance = create_instance(Collection(type=int, container=frozenset))

View File

@ -55,7 +55,7 @@ class GenericCopyTest(unittest.TestCase):
def test_copying_track_to_remove(self):
track = Track(name='foo').copy(name=None)
self.assertEqual(track.__dict__, Track().__dict__)
self.assertFalse(hasattr(track, '_name'))
class RefTest(unittest.TestCase):