Welcome, guest | Sign In | My Account | Store | Cart
NOTE: Recipes have moved! Please visit GitHub.com/activestate/code for the current versions.

NamedShare is a variation on the Singleton pattern (see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/102187). In effect, it provides for a set of quasi-singletons, identified by keyword. If no keyword is given, the default share is accessed.

Python, 50 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class NamedShare(type):
	def __init__( cls, name, bases, classdict ):
		super( NamedShare, cls ).__init__( name, bases, classdict )
		cls.__instances = {}
	def __call__( cls, *args, **kw ):
		if kw.has_key( 'share' ):
			key = kw.get( 'share' )
			del( kw['share'] )
		else:
			key = 'default'
		if not cls.__instances.has_key( key ):
			cls.__instances[key] = super( NamedShare, cls ).__call__( *args, **kw )
		return cls.__instances[key]

# Test/Example Code
if __name__ == '__main__':
	
	class sharedDict( dict ):
		__metaclass__ = NamedShare
	
	class SD2( sharedDict ):
		pass
	
	D1 = sharedDict( a='a', b='b', c='c' )
	D2 = sharedDict( share='share2', d='d', e='e', f='f' )
	D3 = sharedDict( share='share3' )
	D4 = sharedDict()
	D5 = sharedDict( share='share3' )
	
	D6 = SD2()
	
	assert ( D2 is not D1 )
	assert ( D3 is not D1 )
	assert ( D3 is not D2 )
	assert ( D4 is D1 )
	assert ( D5 is D3 )
	assert ( D6 is not D1 )
	
	for s in ['g','h','i']:
		sharedDict(share='share3')[s] = s
	
	for s in ['g','h','i']:
		sharedDict(share='share3')[s] = s
	
	print "D1 (", id(D1), ") =", D1
	print "D2 (", id(D2), ") =", D2
	print "D3 (", id(D3), ") =", D3
	print "D4 (", id(D4), ") =", D4
	print "D5 (", id(D5), ") =", D5
	print "D6 (", id(D6), ") =", D6

I frequently find I need a class that resembles a Singleton, but for which I need more than one instance (but still a small number of instances) of the class. In effect, I need a set of functionally identical Singletons.

For example, when dealing with two separate databases in one application, it would be very convenient to simply refer to them as singletons, but without having to create a distinct singleton class for each database.

I could simply use a class and a dictionary of instances, and make the dictionary globally-visible. But I'd rather have a standardized, pre-tested block of code to implement and manage such a dictionary for me.

(FWIW, I tried to come up with a name that captured both the multiple-instance and the Singleton characteristics; the best I could come up with was MultiSingleton or SingletonGroup, both of which sound truly awful. So NamedShare it is.)

This implementation expects the share identifier to be provided as a named parameter 'share'. As a result, the NamedShare subclass must not use this name for an __init__ parameter.

__init__ calls the default __init__, then adds a dictionary __instances for the share instances.

__call__ caches named and default shares in the __instances dictionary. If the named share is not found, __call__ removes the 'share' parameter, then creates and caches the requested object.

ALTERNATE APPROACH? NOPE

I tried to think up an approach that would allow passing the share identifier as a positional parameter, as in the following:<pre> class C: __metaclass__ = NamedShare def __init__( self, a=None, b=None, c=None ): pass w = C() # (1) default share x = C( 'A', 'B', 'C' ) # (2) default share with params y = C( 'share2', 'A', 'B', 'C' ) # (3) named share with params z = C( 'share2' ) # (4) named share without params </pre> But I just can't see how to distinguish between (3) and (4). And in most cases, I suspect that a single, default share will be sufficient, so I don't want to require that a share identifier be specified for the default share. So it looks like I'm (we're) stuck with an optional named parameter to identify the share.

USAGE

Typically, a share instance would be invoked with parameters as part of application initialization, and a reference stored in the application. It could then be invoked without parameters (or with only the share key) from anywhere in the program. The later invocations would then be accessing the same shared object, but would need neither know nor care what parameters were passed to create the share.

An example:<pre> class C: __metaclass__ = NamedShare def __init__( self, a=None, b=None, c=None ): pass w = C() # (1) default share x = C( 'A', 'B', 'C' ) # (2) default share with params y = C( 'A', 'B', 'C', share='share2' ) # (3) named share with params z = C( share='share2' ) # (4) named share without params </pre>

PERFORMANCE

As with Singleton, if a given code block will reference a share instance repeatedly, it will be more efficient (not to mention cleaner and more compact) to retrieve it once into a local variable, then use the local variable. For example, the following are equivalent:<pre> # less efficient for s in aListOfStrings: sharedDict(share='share3')[s] = s

# more efficient
D = sharedDict(share='share3')
for s in aListOfStrings:
    D[s] = s

</pre>

Created by Samuel Reynolds on Wed, 7 Jul 2004 (PSF)
Python recipes (4591)
Samuel Reynolds's recipes (2)

Required Modules

  • (none specified)

Other Information and Tasks