]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add MemoizedSlots, a generalized solution to using __getattr__
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 6 Jan 2015 00:02:08 +0000 (19:02 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 6 Jan 2015 00:02:08 +0000 (19:02 -0500)
for memoization on a class that uses slots.
- apply many more __slots__.  mem use for nova now at 46% savings

18 files changed:
doc/build/changelog/migration_10.rst
doc/build/core/metadata.rst
doc/build/orm/internals.rst
lib/sqlalchemy/event/attr.py
lib/sqlalchemy/ext/associationproxy.py
lib/sqlalchemy/ext/hybrid.py
lib/sqlalchemy/orm/base.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/util.py
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/_collections.py
lib/sqlalchemy/util/langhelpers.py

index e5382be542d64f26b2b6c739054188541a7680f3..8870cfd7e3f073ada5264f650752a2b8a808ee5f 100644 (file)
@@ -300,7 +300,7 @@ internals, comparator objects and parts of the ORM attribute and
 loader strategy system.
 
 A bench that makes use of heapy measure the startup size of Nova
-illustrates a difference of about 2 megs of memory, a total of 27%
+illustrates a difference of about 2 megs of memory, a total of 46%
 of memory taken up by SQLAlchemy's objects, associated dictionaries, as
 well as weakrefs, within a basic import of "nova.db.sqlalchemy.models"::
 
@@ -308,13 +308,13 @@ well as weakrefs, within a basic import of "nova.db.sqlalchemy.models"::
     # associated dicts + weakref-related objects with core of Nova imported:
 
         Before: total count 26477 total bytes 7975712
-        After: total count 21413 total bytes 5752976
+        After: total count 18181 total bytes 4236456
 
     # reported for the Python module space overall with the
     # core of Nova imported:
 
         Before: Partition of a set of 355558 objects. Total size = 61661760 bytes.
-        After: Partition of a set of 350281 objects. Total size = 59415104 bytes.
+        After: Partition of a set of 346034 objects. Total size = 57808016 bytes.
 
 
 .. _feature_updatemany:
index d6fc8c6afd860f7ec04ee91988eabc06e7758b53..e46217c178d2485aedc7ad6282bba9653f23ac3a 100644 (file)
@@ -316,6 +316,7 @@ Column, Table, MetaData API
 
 .. autoclass:: SchemaItem
     :members:
+    :undoc-members:
 
 .. autoclass:: Table
     :members:
index bead784a3b4b91f06729abcb4e02842c4afa2591..debb1ab7e24e780a9a5e4877ef2d5a79219f6a0e 100644 (file)
@@ -38,6 +38,8 @@ sections, are listed here.
 .. autoclass:: sqlalchemy.orm.base.InspectionAttr
     :members:
 
+.. autoclass:: sqlalchemy.orm.base.InspectionAttrInfo
+    :members:
 
 .. autoclass:: sqlalchemy.orm.state.InstanceState
     :members:
@@ -54,6 +56,29 @@ sections, are listed here.
 .. autoclass:: sqlalchemy.orm.interfaces.MapperProperty
     :members:
 
+    .. py:attribute:: info
+
+        Info dictionary associated with the object, allowing user-defined
+        data to be associated with this :class:`.InspectionAttr`.
+
+        The dictionary is generated when first accessed.  Alternatively,
+        it can be specified as a constructor argument to the
+        :func:`.column_property`, :func:`.relationship`, or :func:`.composite`
+        functions.
+
+        .. versionadded:: 0.8  Added support for .info to all
+           :class:`.MapperProperty` subclasses.
+
+        .. versionchanged:: 1.0.0 :attr:`.InspectionAttr.info` moved
+           from :class:`.MapperProperty` so that it can apply to a wider
+           variety of ORM and extension constructs.
+
+        .. seealso::
+
+            :attr:`.QueryableAttribute.info`
+
+            :attr:`.SchemaItem.info`
+
 .. autodata:: sqlalchemy.orm.interfaces.NOT_EXTENSION
 
 
index de5d34950f6dd49230c5f529143d5d784cd91c65..ed1dca64417a092e4a286951c15bab70f4cf8d4b 100644 (file)
@@ -40,15 +40,19 @@ import weakref
 import collections
 
 
-class RefCollection(object):
-    @util.memoized_property
-    def ref(self):
+class RefCollection(util.MemoizedSlots):
+    __slots__ = 'ref',
+
+    def _memoized_attr_ref(self):
         return weakref.ref(self, registry._collection_gced)
 
 
 class _ClsLevelDispatch(RefCollection):
     """Class-level events on :class:`._Dispatch` classes."""
 
+    __slots__ = ('name', 'arg_names', 'has_kw',
+                 'legacy_signatures', '_clslevel')
+
     def __init__(self, parent_dispatch_cls, fn):
         self.name = fn.__name__
         argspec = util.inspect_getargspec(fn)
@@ -60,8 +64,7 @@ class _ClsLevelDispatch(RefCollection):
                 key=lambda s: s[0]
             )
         ))
-        self.__doc__ = fn.__doc__ = legacy._augment_fn_docs(
-            self, parent_dispatch_cls, fn)
+        fn.__doc__ = legacy._augment_fn_docs(self, parent_dispatch_cls, fn)
 
         self._clslevel = weakref.WeakKeyDictionary()
 
@@ -158,7 +161,7 @@ class _ClsLevelDispatch(RefCollection):
         return self
 
 
-class _InstanceLevelDispatch(object):
+class _InstanceLevelDispatch(RefCollection):
     __slots__ = ()
 
     def _adjust_fn_spec(self, fn, named):
@@ -229,10 +232,9 @@ class _EmptyListener(_InstanceLevelDispatch):
 class _CompoundListener(_InstanceLevelDispatch):
     _exec_once = False
 
-    __slots__ = ()
+    __slots__ = '_exec_once_mutex',
 
-    @util.memoized_property
-    def _exec_once_mutex(self):
+    def _memoized_attr__exec_once_mutex(self):
         return threading.Lock()
 
     def exec_once(self, *args, **kw):
@@ -267,7 +269,7 @@ class _CompoundListener(_InstanceLevelDispatch):
     __nonzero__ = __bool__
 
 
-class _ListenerCollection(RefCollection, _CompoundListener):
+class _ListenerCollection(_CompoundListener):
     """Instance-level attributes on instances of :class:`._Dispatch`.
 
     Represents a collection of listeners.
@@ -277,8 +279,7 @@ class _ListenerCollection(RefCollection, _CompoundListener):
 
     """
 
-    # RefCollection has a @memoized_property, so can't do
-    # __slots__ here
+    __slots__ = 'parent_listeners', 'parent', 'name', 'listeners', 'propagate'
 
     def __init__(self, parent, target_cls):
         if target_cls not in parent._clslevel:
index 1aa68ac3275f3032c30efab69056ec5e5f5dd048..bb08ce9ba678655836fdf4f252d32db18b10263c 100644 (file)
@@ -86,7 +86,7 @@ ASSOCIATION_PROXY = util.symbol('ASSOCIATION_PROXY')
 """
 
 
-class AssociationProxy(interfaces.InspectionAttr):
+class AssociationProxy(interfaces.InspectionAttrInfo):
     """A descriptor that presents a read/write view of an object attribute."""
 
     is_attribute = False
index d89a13fc9a5a7532ef527e344ec5411157e5fc83..f72de60992f1d104186ce47063fc5d5cf6e632b1 100644 (file)
@@ -660,7 +660,7 @@ HYBRID_PROPERTY = util.symbol('HYBRID_PROPERTY')
 """
 
 
-class hybrid_method(interfaces.InspectionAttr):
+class hybrid_method(interfaces.InspectionAttrInfo):
     """A decorator which allows definition of a Python object method with both
     instance-level and class-level behavior.
 
@@ -703,7 +703,7 @@ class hybrid_method(interfaces.InspectionAttr):
         return self
 
 
-class hybrid_property(interfaces.InspectionAttr):
+class hybrid_property(interfaces.InspectionAttrInfo):
     """A decorator which allows definition of a Python descriptor with both
     instance-level and class-level behavior.
 
index afeeba3226e18432238e6d555daf0e5714eb1abc..c5c8c5e2e45a5ab71b739eea74906501567333ff 100644 (file)
@@ -437,7 +437,6 @@ class InspectionAttr(object):
     here intact for forwards-compatibility.
 
     """
-
     __slots__ = ()
 
     is_selectable = False
@@ -490,6 +489,12 @@ class InspectionAttr(object):
 
     """
 
+
+class InspectionAttrInfo(InspectionAttr):
+    """Adds the ``.info`` attribute to :class:`.Inspectionattr`.
+
+    """
+
     @util.memoized_property
     def info(self):
         """Info dictionary associated with the object, allowing user-defined
index 19ff71f73fdb5fc8921ef608a21713c304050bd1..e68ff1bea69be5fa5bf2c15025dbdea0f1a04282 100644 (file)
@@ -143,6 +143,7 @@ class CompositeProperty(DescriptorProperty):
           class.  **Deprecated.**  Please see :class:`.AttributeEvents`.
 
         """
+        super(CompositeProperty, self).__init__()
 
         self.attrs = attrs
         self.composite_class = class_
@@ -471,6 +472,7 @@ class ConcreteInheritedProperty(DescriptorProperty):
         return comparator_callable
 
     def __init__(self):
+        super(ConcreteInheritedProperty, self).__init__()
         def warn():
             raise AttributeError("Concrete %s does not implement "
                                  "attribute %r at the instance level.  Add "
@@ -555,6 +557,7 @@ class SynonymProperty(DescriptorProperty):
             more complicated attribute-wrapping schemes than synonyms.
 
         """
+        super(SynonymProperty, self).__init__()
 
         self.name = name
         self.map_column = map_column
@@ -684,6 +687,7 @@ class ComparableProperty(DescriptorProperty):
             .. versionadded:: 1.0.0
 
         """
+        super(ComparableProperty, self).__init__()
         self.descriptor = descriptor
         self.comparator_factory = comparator_factory
         self.doc = doc or (descriptor and descriptor.__doc__) or None
index 68b86268cad9f7b70e680ebdae7fcdf4054c52bf..346e2412e8dac5f503ec1ec7c987d985f7e88c20 100644 (file)
@@ -24,7 +24,8 @@ from .. import util
 from ..sql import operators
 from .base import (ONETOMANY, MANYTOONE, MANYTOMANY,
                    EXT_CONTINUE, EXT_STOP, NOT_EXTENSION)
-from .base import InspectionAttr, _MappedAttribute
+from .base import (InspectionAttr, InspectionAttr,
+    InspectionAttrInfo, _MappedAttribute)
 import collections
 
 # imported later
@@ -48,7 +49,7 @@ __all__ = (
 )
 
 
-class MapperProperty(_MappedAttribute, InspectionAttr):
+class MapperProperty(_MappedAttribute, InspectionAttr, util.MemoizedSlots):
     """Manage the relationship of a ``Mapper`` to a single class
     attribute, as well as that attribute as it appears on individual
     instances of the class, including attribute instrumentation,
@@ -63,6 +64,11 @@ class MapperProperty(_MappedAttribute, InspectionAttr):
 
     """
 
+    __slots__ = (
+        '_configure_started', '_configure_finished', 'parent', 'key',
+        'info'
+    )
+
     cascade = frozenset()
     """The set of 'cascade' attribute names.
 
@@ -78,6 +84,32 @@ class MapperProperty(_MappedAttribute, InspectionAttr):
 
     """
 
+    def _memoized_attr_info(self):
+        """Info dictionary associated with the object, allowing user-defined
+        data to be associated with this :class:`.InspectionAttr`.
+
+        The dictionary is generated when first accessed.  Alternatively,
+        it can be specified as a constructor argument to the
+        :func:`.column_property`, :func:`.relationship`, or :func:`.composite`
+        functions.
+
+        .. versionadded:: 0.8  Added support for .info to all
+           :class:`.MapperProperty` subclasses.
+
+        .. versionchanged:: 1.0.0 :attr:`.InspectionAttr.info` moved
+           from :class:`.MapperProperty` so that it can apply to a wider
+           variety of ORM and extension constructs.
+
+        .. seealso::
+
+            :attr:`.QueryableAttribute.info`
+
+            :attr:`.SchemaItem.info`
+
+        """
+        return {}
+
+
     def setup(self, context, entity, path, adapter, **kwargs):
         """Called by Query for the purposes of constructing a SQL statement.
 
@@ -139,8 +171,9 @@ class MapperProperty(_MappedAttribute, InspectionAttr):
 
         """
 
-    _configure_started = False
-    _configure_finished = False
+    def __init__(self):
+        self._configure_started = False
+        self._configure_finished = False
 
     def init(self):
         """Called after all mappers are created to assemble
@@ -422,6 +455,8 @@ class StrategizedProperty(MapperProperty):
 
     """
 
+    __slots__ = '_strategies', 'strategy'
+
     strategy_wildcard_key = None
 
     def _get_context_loader(self, context, path):
@@ -485,14 +520,14 @@ class StrategizedProperty(MapperProperty):
                 not mapper.class_manager._attr_has_impl(self.key):
             self.strategy.init_class_attribute(mapper)
 
-    _strategies = collections.defaultdict(dict)
+    _all_strategies = collections.defaultdict(dict)
 
     @classmethod
     def strategy_for(cls, **kw):
         def decorate(dec_cls):
             dec_cls._strategy_keys = []
             key = tuple(sorted(kw.items()))
-            cls._strategies[cls][key] = dec_cls
+            cls._all_strategies[cls][key] = dec_cls
             dec_cls._strategy_keys.append(key)
             return dec_cls
         return decorate
@@ -500,8 +535,8 @@ class StrategizedProperty(MapperProperty):
     @classmethod
     def _strategy_lookup(cls, *key):
         for prop_cls in cls.__mro__:
-            if prop_cls in cls._strategies:
-                strategies = cls._strategies[prop_cls]
+            if prop_cls in cls._all_strategies:
+                strategies = cls._all_strategies[prop_cls]
                 try:
                     return strategies[key]
                 except KeyError:
index 9fe6b77f0557e10813ca52bcaa19cedfb0af7c9c..0469c213930ad892574eea1414d4a62a4fc36d1c 100644 (file)
@@ -2787,6 +2787,8 @@ def _event_on_init(state, args, kwargs):
 class _ColumnMapping(dict):
     """Error reporting helper for mapper._columntoproperty."""
 
+    __slots__ = 'mapper',
+
     def __init__(self, mapper):
         self.mapper = mapper
 
index 291fabdd0e31c7b92861525db9ca87a73f11f872..d51b6920d7874afcb4058fa0cd3aab60613b38fd 100644 (file)
@@ -34,6 +34,13 @@ class ColumnProperty(StrategizedProperty):
 
     strategy_wildcard_key = 'column'
 
+    __slots__ = (
+        '_orig_columns', 'columns', 'group', 'deferred',
+        'instrument', 'comparator_factory', 'descriptor', 'extension',
+        'active_history', 'expire_on_flush', 'info', 'doc',
+        'strategy_class', '_creation_order', '_is_polymorphic_discriminator',
+        '_mapped_by_synonym')
+
     def __init__(self, *columns, **kwargs):
         """Provide a column-level property for use with a Mapper.
 
@@ -109,6 +116,7 @@ class ColumnProperty(StrategizedProperty):
             **Deprecated.** Please see :class:`.AttributeEvents`.
 
         """
+        super(ColumnProperty, self).__init__()
         self._orig_columns = [expression._labeled(c) for c in columns]
         self.columns = [expression._labeled(_orm_full_deannotate(c))
                         for c in columns]
@@ -206,7 +214,7 @@ class ColumnProperty(StrategizedProperty):
         elif dest_state.has_identity and self.key not in dest_dict:
             dest_state._expire_attributes(dest_dict, [self.key])
 
-    class Comparator(PropComparator):
+    class Comparator(util.MemoizedSlots, PropComparator):
         """Produce boolean, comparison, and other operators for
         :class:`.ColumnProperty` attributes.
 
@@ -225,8 +233,9 @@ class ColumnProperty(StrategizedProperty):
 
         """
 
-        @util.memoized_instancemethod
-        def __clause_element__(self):
+        __slots__ = '__clause_element__', 'info'
+
+        def _memoized_method___clause_element__(self):
             if self.adapter:
                 return self.adapter(self.prop.columns[0])
             else:
@@ -234,15 +243,14 @@ class ColumnProperty(StrategizedProperty):
                     "parententity": self._parentmapper,
                     "parentmapper": self._parentmapper})
 
-        @util.memoized_property
-        def info(self):
+        def _memoized_attr_info(self):
             ce = self.__clause_element__()
             try:
                 return ce.info
             except AttributeError:
                 return self.prop.info
 
-        def __getattr__(self, key):
+        def _fallback_getattr(self, key):
             """proxy attribute access down to the mapped column.
 
             this allows user-defined comparison methods to be accessed.
index d3ae107b906c916fb7f203d12e41113f4594942f..df2250a4ca912454c85a57ae1e14d85390ad0e3b 100644 (file)
@@ -775,6 +775,7 @@ class RelationshipProperty(StrategizedProperty):
 
 
         """
+        super(RelationshipProperty, self).__init__()
 
         self.uselist = uselist
         self.argument = argument
index 4be8d19ff3f9b3df3f426432c51387ada817d88c..ee629b0343f5901d117a99d6c4cee83c92c6a8a7 100644 (file)
@@ -30,6 +30,10 @@ class CascadeOptions(frozenset):
         'all', 'none', 'delete-orphan'])
     _allowed_cascades = all_cascades
 
+    __slots__ = (
+        'save_update', 'delete', 'refresh_expire', 'merge',
+        'expunge', 'delete_orphan')
+
     def __new__(cls, value_list):
         if isinstance(value_list, str) or value_list is None:
             return cls.from_string(value_list)
@@ -38,10 +42,7 @@ class CascadeOptions(frozenset):
             raise sa_exc.ArgumentError(
                 "Invalid cascade option(s): %s" %
                 ", ".join([repr(x) for x in
-                           sorted(
-                    values.difference(cls._allowed_cascades)
-                )])
-            )
+                           sorted(values.difference(cls._allowed_cascades))]))
 
         if "all" in values:
             values.update(cls._add_w_all_cascades)
@@ -76,6 +77,7 @@ class CascadeOptions(frozenset):
         ]
         return cls(values)
 
+
 def _validator_events(
         desc, key, validator, include_removes, include_backrefs):
     """Runs a validation method on an attribute value to be set or
index 2d06109b9780f67aabd8f02923f9f511d181d052..0f640530939af48013e3639dbed32bc9eff5f20f 100644 (file)
@@ -449,10 +449,12 @@ class ColumnCollection(util.OrderedProperties):
 
     """
 
+    __slots__ = '_all_col_set', '_all_columns'
+
     def __init__(self, *columns):
         super(ColumnCollection, self).__init__()
-        self.__dict__['_all_col_set'] = util.column_set()
-        self.__dict__['_all_columns'] = []
+        object.__setattr__(self, '_all_col_set', util.column_set())
+        object.__setattr__(self, '_all_columns', [])
         for c in columns:
             self.add(c)
 
@@ -576,13 +578,14 @@ class ColumnCollection(util.OrderedProperties):
         return util.OrderedProperties.__contains__(self, other)
 
     def __getstate__(self):
-        return {'_data': self.__dict__['_data'],
-                '_all_columns': self.__dict__['_all_columns']}
+        return {'_data': self._data,
+                '_all_columns': self._all_columns}
 
     def __setstate__(self, state):
-        self.__dict__['_data'] = state['_data']
-        self.__dict__['_all_columns'] = state['_all_columns']
-        self.__dict__['_all_col_set'] = util.column_set(state['_all_columns'])
+        object.__setattr__(self, '_data', state['_data'])
+        object.__setattr__(self, '_all_columns', state['_all_columns'])
+        object.__setattr__(
+            self, '_all_col_set', util.column_set(state['_all_columns']))
 
     def contains_column(self, col):
         # this has to be done via set() membership
@@ -596,8 +599,8 @@ class ColumnCollection(util.OrderedProperties):
 class ImmutableColumnCollection(util.ImmutableProperties, ColumnCollection):
     def __init__(self, data, colset, all_columns):
         util.ImmutableProperties.__init__(self, data)
-        self.__dict__['_all_col_set'] = colset
-        self.__dict__['_all_columns'] = all_columns
+        object.__setattr__(self, '_all_col_set', colset)
+        object.__setattr__(self, '_all_columns', all_columns)
 
     extend = remove = util.ImmutableProperties._immutable
 
index 445857b821c396066d2fb1ed88b4db2413eb0b0a..8df22b7a637c9468bd57b6eb5cd64c8c33b5eeb4 100644 (file)
@@ -3387,7 +3387,7 @@ class ReleaseSavepointClause(_IdentifiedClause):
     __visit_name__ = 'release_savepoint'
 
 
-class quoted_name(util.text_type):
+class quoted_name(util.MemoizedSlots, util.text_type):
     """Represent a SQL identifier combined with quoting preferences.
 
     :class:`.quoted_name` is a Python unicode/str subclass which
@@ -3431,6 +3431,8 @@ class quoted_name(util.text_type):
 
     """
 
+    __slots__ = 'quote', 'lower', 'upper'
+
     def __new__(cls, value, quote):
         if value is None:
             return None
@@ -3450,15 +3452,13 @@ class quoted_name(util.text_type):
     def __reduce__(self):
         return quoted_name, (util.text_type(self), self.quote)
 
-    @util.memoized_instancemethod
-    def lower(self):
+    def _memoized_method_lower(self):
         if self.quote:
             return self
         else:
             return util.text_type(self).lower()
 
-    @util.memoized_instancemethod
-    def upper(self):
+    def _memoized_method_upper(self):
         if self.quote:
             return self
         else:
@@ -3475,6 +3475,8 @@ class _truncated_label(quoted_name):
     """A unicode subclass used to identify symbolic "
     "names that may require truncation."""
 
+    __slots__ = ()
+
     def __new__(cls, value, quote=None):
         quote = getattr(value, "quote", quote)
         # return super(_truncated_label, cls).__new__(cls, value, quote, True)
@@ -3531,6 +3533,7 @@ class conv(_truncated_label):
         :ref:`constraint_naming_conventions`
 
     """
+    __slots__ = ()
 
 
 class _defer_name(_truncated_label):
@@ -3538,6 +3541,8 @@ class _defer_name(_truncated_label):
     generation.
 
     """
+    __slots__ = ()
+
     def __new__(cls, value):
         if value is None:
             return _NONE_NAME
@@ -3552,6 +3557,7 @@ class _defer_name(_truncated_label):
 
 class _defer_none_name(_defer_name):
     """indicate a 'deferred' name that was ultimately the value None."""
+    __slots__ = ()
 
 _NONE_NAME = _defer_none_name("_unnamed_")
 
@@ -3566,6 +3572,8 @@ class _anonymous_label(_truncated_label):
     """A unicode subclass used to identify anonymously
     generated names."""
 
+    __slots__ = ()
+
     def __add__(self, other):
         return _anonymous_label(
             quoted_name(
index 7c85ef94b4ea8723d297c41c5adbf1e399ffd0ed..c23b0196f370fa5b0e5b323a12d7a5516a455d2f 100644 (file)
@@ -36,7 +36,7 @@ from .langhelpers import iterate_attributes, class_hierarchy, \
     generic_repr, counter, PluginLoader, hybridproperty, hybridmethod, \
     safe_reraise,\
     get_callable_argspec, only_once, attrsetter, ellipses_string, \
-    warn_limited, map_bits
+    warn_limited, map_bits, MemoizedSlots
 
 from .deprecations import warn_deprecated, warn_pending_deprecation, \
     deprecated, pending_deprecation, inject_docstring_text
index d368526980285ee2da75d41dad6f361590b1689f..0f05e3427d3558922245b5b5edc6e83b0d6985aa 100644 (file)
@@ -179,8 +179,10 @@ class immutabledict(ImmutableContainer, dict):
 class Properties(object):
     """Provide a __getattr__/__setattr__ interface over a dict."""
 
+    __slots__ = '_data',
+
     def __init__(self, data):
-        self.__dict__['_data'] = data
+        object.__setattr__(self, '_data', data)
 
     def __len__(self):
         return len(self._data)
@@ -200,8 +202,8 @@ class Properties(object):
     def __delitem__(self, key):
         del self._data[key]
 
-    def __setattr__(self, key, object):
-        self._data[key] = object
+    def __setattr__(self, key, obj):
+        self._data[key] = obj
 
     def __getstate__(self):
         return {'_data': self.__dict__['_data']}
@@ -252,6 +254,8 @@ class OrderedProperties(Properties):
     """Provide a __getattr__/__setattr__ interface with an OrderedDict
     as backing store."""
 
+    __slots__ = ()
+
     def __init__(self):
         Properties.__init__(self, OrderedDict())
 
@@ -259,10 +263,17 @@ class OrderedProperties(Properties):
 class ImmutableProperties(ImmutableContainer, Properties):
     """Provide immutable dict/object attribute to an underlying dictionary."""
 
+    __slots__ = ()
+
 
 class OrderedDict(dict):
     """A dict that returns keys/values/items in the order they were added."""
 
+    __slots__ = '_list',
+
+    def __reduce__(self):
+        return OrderedDict, (self.items(),)
+
     def __init__(self, ____sequence=None, **kwargs):
         self._list = []
         if ____sequence is None:
index b708665f9d9311c5042aac22a31f20c168697a59..22b6ad4ca7b0ce2df8efe6eaf7e7a2e62e65132e 100644 (file)
@@ -522,6 +522,15 @@ class portable_instancemethod(object):
 
     """
 
+    __slots__ = 'target', 'name', '__weakref__'
+
+    def __getstate__(self):
+        return {'target': self.target, 'name': self.name}
+
+    def __setstate__(self, state):
+        self.target = state['target']
+        self.name = state['name']
+
     def __init__(self, meth):
         self.target = meth.__self__
         self.name = meth.__name__
@@ -800,6 +809,40 @@ class group_expirable_memoized_property(object):
         return memoized_instancemethod(fn)
 
 
+class MemoizedSlots(object):
+    """Apply memoized items to an object using a __getattr__ scheme.
+
+    This allows the functionality of memoized_property and
+    memoized_instancemethod to be available to a class using __slots__.
+
+    """
+
+    def _fallback_getattr(self, key):
+        raise AttributeError(key)
+
+    def __getattr__(self, key):
+        if key.startswith('_memoized'):
+            raise AttributeError(key)
+        elif hasattr(self, '_memoized_attr_%s' % key):
+            value = getattr(self, '_memoized_attr_%s' % key)()
+            setattr(self, key, value)
+            return value
+        elif hasattr(self, '_memoized_method_%s' % key):
+            fn = getattr(self, '_memoized_method_%s' % key)
+
+            def oneshot(*args, **kw):
+                result = fn(*args, **kw)
+                memo = lambda *a, **kw: result
+                memo.__name__ = fn.__name__
+                memo.__doc__ = fn.__doc__
+                setattr(self, key, memo)
+                return result
+            oneshot.__doc__ = fn.__doc__
+            return oneshot
+        else:
+            return self._fallback_getattr(key)
+
+
 def dependency_for(modulename):
     def decorate(obj):
         # TODO: would be nice to improve on this import silliness,