Add a new metaclass cacher.Singleton.
authorNeil Moore <neil@s-z.org>
Tue, 27 May 2008 19:23:41 +0000 (15:23 -0400)
committerNeil Moore <neil@s-z.org>
Tue, 27 May 2008 19:23:41 +0000 (15:23 -0400)
Add another couple of tests to cacher.FirstArg

cacher.py

index c126672..aec4e97 100644 (file)
--- a/cacher.py
+++ b/cacher.py
@@ -35,7 +35,7 @@ class FirstArg(type):
 
 
     In fact, if an instance has been constructed, the remaining
-    parameters are disregarded, and can be omitted
+    parameters are disregarded, and can be omitted:
 
     >>> MyClass('foo') is obj
     True
@@ -50,6 +50,7 @@ class FirstArg(type):
     >>> MyClass('bar').extra
     18
 
+
     Any immutable key may be used:
 
     >>> pairobj = MyClass((1,2), {})
@@ -62,6 +63,15 @@ class FirstArg(type):
         ....
     TypeError: list objects are unhashable
 
+
+    However, the key None will not be cached:
+
+    >>> noneobj = ( MyClass(None, 'first'), MyClass(None, 'second') )
+    >>> noneobj[0].extra
+    'first'
+    >>> noneobj[1].extra
+    'second'
+
    
     The objects themselves can be mutable:
 
@@ -87,13 +97,85 @@ class FirstArg(type):
     def __init__(cls, name, bases, cdict):
         super(FirstArg, cls).__init__(name, bases, cdict)
         cls.__instcache = {}
-    def __call__( cls, name, *args, **kw ):
+    def __call__(cls, name, *args, **kw):
         
         if name is None or name not in cls.__instcache:
             cls.__instcache[name] = super(FirstArg, cls).__call__(
                     name, *args, **kw )
         return cls.__instcache[name]
 
+class Singleton (type):
+    """Metaclass for singleton objects.  After the first constructor call
+    initializes an object, subsequent calls will return the same object.
+    
+    >>> import cacher
+    >>> class MyClass (object):
+    ...     __metaclass__ = cacher.Singleton
+    ...     def __init__(self, data):
+    ...         self.data = data
+
+
+    The constructor must be called with the correct number of arguments:
+
+    >>> bad = MyClass()
+    Traceback (most recent call last):
+        ....
+    TypeError: __init__() takes exactly 2 arguments (1 given)
+    >>> obj = MyClass('only')
+
+
+    After an object is successfully constructed, subsequent calls to the
+    constructor yield the same object:
+
+    >>> MyClass('only') is obj
+    True
+
+    
+    This is true even if the other parameters differ:
+
+    >>> MyClass('another') is obj
+    True
+    >>> MyClass('ignored').data
+    'only'
+
+
+    In fact, once an instance has been constructed, subsequent calls to
+    the constructor ignore their parameters:
+
+    >>> MyClass() is obj
+    True
+    >>> MyClass(1, 2, 3, 4, 5, 6, 7) is obj
+    True
+    
+    The objects can be mutable:
+
+    >>> MyClass().data = 'changed'
+    >>> obj.data
+    'changed'
+
+    
+    The cache is not shared with subclasses:
+
+    >>> class SubClass (MyClass):
+    ...     def __init__(self, data):
+    ...         super(SubClass,self).__init__(data)
+    >>> sub = SubClass('foo')
+    >>> sub is obj
+    False
+    >>> sub.data
+    'foo'
+    >>> MyClass('foo').data
+    'changed'
+    """
+    def __init__(cls, name, bases, cdict):
+        super(Singleton, cls).__init__(name, bases, cdict)
+        cls.__instance = None
+
+    def __call__(cls, *args, **kw):
+        if cls.__instance is None:
+            cls.__instance = super(Singleton, cls).__call__(*args, **kw)
+        return cls.__instance
+
 if __name__ == "__main__":
     import doctest
     doctest.testmod()