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

View File

@ -29,15 +29,14 @@ class FieldDescriptorTest(unittest.TestCase):
instance = create_instance(Field()) instance = create_instance(Field())
self.assertIsNone(instance.attr) 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()) instance = create_instance(Field())
self.assertNotIn('attr', instance.__dict__) self.assertFalse(hasattr(instance, '_attr'))
def test_field_assigment_and_retrival(self): def test_field_assigment_and_retrival(self):
instance = create_instance(Field()) instance = create_instance(Field())
instance.attr = 1234 instance.attr = 1234
self.assertEqual(1234, instance.attr) self.assertEqual(1234, instance.attr)
self.assertEqual(1234, instance.__dict__['attr'])
def test_field_can_be_reassigned(self): def test_field_can_be_reassigned(self):
instance = create_instance(Field()) instance = create_instance(Field())
@ -50,14 +49,14 @@ class FieldDescriptorTest(unittest.TestCase):
instance.attr = 1234 instance.attr = 1234
del instance.attr del instance.attr
self.assertEqual(None, 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): def test_field_can_be_set_to_none(self):
instance = create_instance(Field()) instance = create_instance(Field())
instance.attr = 1234 instance.attr = 1234
instance.attr = None instance.attr = None
self.assertEqual(None, instance.attr) self.assertEqual(None, instance.attr)
self.assertNotIn('attr', instance.__dict__) self.assertFalse(hasattr(instance, '_attr'))
def test_field_can_be_set_default(self): def test_field_can_be_set_default(self):
default = object() default = object()
@ -65,7 +64,7 @@ class FieldDescriptorTest(unittest.TestCase):
instance.attr = 1234 instance.attr = 1234
instance.attr = default instance.attr = default
self.assertEqual(default, instance.attr) self.assertEqual(default, instance.attr)
self.assertNotIn('attr', instance.__dict__) self.assertFalse(hasattr(instance, '_attr'))
class FieldTest(unittest.TestCase): class FieldTest(unittest.TestCase):
@ -183,13 +182,11 @@ class CollectionTest(unittest.TestCase):
instance = create_instance(Collection(type=int, container=frozenset)) instance = create_instance(Collection(type=int, container=frozenset))
instance.attr = [] instance.attr = []
self.assertEqual(frozenset(), instance.attr) self.assertEqual(frozenset(), instance.attr)
self.assertNotIn('attr', instance.__dict__)
def test_collection_gets_stored_in_container(self): def test_collection_gets_stored_in_container(self):
instance = create_instance(Collection(type=int, container=frozenset)) instance = create_instance(Collection(type=int, container=frozenset))
instance.attr = [1, 2, 3] instance.attr = [1, 2, 3]
self.assertEqual(frozenset([1, 2, 3]), instance.attr) self.assertEqual(frozenset([1, 2, 3]), instance.attr)
self.assertEqual(frozenset([1, 2, 3]), instance.__dict__['attr'])
def test_collection_with_wrong_type(self): def test_collection_with_wrong_type(self):
instance = create_instance(Collection(type=int, container=frozenset)) 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): def test_copying_track_to_remove(self):
track = Track(name='foo').copy(name=None) track = Track(name='foo').copy(name=None)
self.assertEqual(track.__dict__, Track().__dict__) self.assertFalse(hasattr(track, '_name'))
class RefTest(unittest.TestCase): class RefTest(unittest.TestCase):