singleton objects with decorators

U

Uwe Mayer

Tuesday said:
I did not put memoize on __new__. I put it on the metaclass __call__.
Here is my memoize:

def memoize(func):
memoize_dic = {}
def wrapped_func(*args):
if args in memoize_dic:
return memoize_dic[args]
else:
result = func(*args)
memoize_dic[args] = result
return result
wrapped_func.__name__ = func.__name__
wrapped_func.__doc__ = func.__doc__
wrapped_func.__dict__ = func.__dict__
return wrapped_func

class Memoize(type): # Singleton is a special case of Memoize
@memoize
def __call__(cls, *args):
return super(Memoize, cls).__call__(*args)

I tried it out and found the following "inconsistency":
.... __metaclass__ = Memoize
.... def __init__(self, *args): pass
....<__main__.Foobar object at 0x4006f82c>

Unless I'm using it incorrectly (I haven't done much metaclass programming
yet) this is the same problem was with using @memoize on __new__ and
__init__.

Talking so much about singletons I'm not even sure what the definition on
calling a singleton with different constructor parameter values is.

Anyways, a fix for that could be:

class SingletonFactory(type):
single = {}
def __call__(cls, *args):
if (cls not in SingletonFactory.single):
SingletonFactory.single[cls] = super(SingletonFactory,
cls).__call__(*args)
return SingletonFactory.single[cls]


i.e. not caching the parameter values or types.

Uwe
 
M

Michele Simionato

Uhm? If I pass different parameters I want to have
different instances. The Singleton behaviour is recovered
only when I pass always the same arguments, in
particular when I pass 0-arguments:
.... __metaclass__ = Memoize
....<__main__.Foobar object at 0xb7defbcc>

Of course if for Singleton you mean "whatever I pass
to the constructor it must always return the same
instance" then this pattern is not a Singleton.
This is why I call it memoize ;)
 
U

Uwe Mayer

Tuesday said:
Uhm? If I pass different parameters I want to have
different instances. The Singleton behaviour is recovered
only when I pass always the same arguments, in
particular when I pass 0-arguments:

... __metaclass__ = Memoize
...
<__main__.Foobar object at 0xb7defbcc>

Of course if for Singleton you mean "whatever I pass
to the constructor it must always return the same
instance" then this pattern is not a Singleton.
This is why I call it memoize ;)

:)

I guess it depends on what you want to do with the instance and the
constructor, wether it satisfies the condition to be a "singleton":

If you pass i.e. the timestamp of object creation in the constructor, or the
class name that instanciates the object, a memoized implementation would
not suffice.

On the other hand if you need parameterized implementations of a singleton,
i.e. for each colour 'red', 'green', 'blue' - then a memoized
implementation would be better.

Uwe
 
S

Steve Holden

Bengt Richter wrote:
[...]
It's a weird beast, being a subtype of int also. I'll defer to the BDFL in
http://www.python.org/peps/pep-0285.html

"""
The values False and True will be singletons, like None. Because
the type has two values, perhaps these should be called
"doubletons"? The real implementation will not allow other
instances of bool to be created.
"""

Regards,
Bengt Richter

It would probably make sense (IMHO) to deny rebinding of True and False
in the same way that 2.4 denies rebinding of None. Given that people
would set True and False themselves in order to make their pre-2.4 code
readable, however, this may overstress backward compatibility.

regards
Steve
 
B

Bengt Richter

I did not put memoize on __new__. I put it on the metaclass __call__.
Here is my memoize:

def memoize(func):
memoize_dic = {}
def wrapped_func(*args):
if args in memoize_dic:
return memoize_dic[args]
else:
result = func(*args)
memoize_dic[args] = result
return result
wrapped_func.__name__ = func.__name__
wrapped_func.__doc__ = func.__doc__
wrapped_func.__dict__ = func.__dict__
return wrapped_func

class Memoize(type): # Singleton is a special case of Memoize
@memoize
def __call__(cls, *args):
return super(Memoize, cls).__call__(*args)
Thanks, that is nice and simple, though caching instances of a class
according to initialization parameters is not quite the same concept
as singleton instances, I think. OTOH, if you want to pass differing
parameters to the same instance of a class, there are lots of methods
(pun ;-) to do that that are clearer than (ab)using the constructor interface.
I.e., what is the difference between shared access to a callable instance
in a module vs shared access to a strange class in the same place?

Hm, just had a thought re memoize: you could give it its own optional
hashing function as a keyword argument, and let it use that as a key
for memoize_dic. Then you could use that to make a singleton(-making) class
or a dual/doubleton-making class like bool, e.g., Bool below

----< memoize.py >--------------------------------------------------------
def memoize(arghash=lambda args:args, method=False):
def _memoize(func):
memoize_dic = {}
def wrapped_func(*args):
key = arghash(args[method:])
if key in memoize_dic:
return memoize_dic[key]
else:
result = func(*args)
memoize_dic[key] = result
return result
wrapped_func.__name__ = func.__name__
wrapped_func.__doc__ = func.__doc__
wrapped_func.__dict__ = func.__dict__
return wrapped_func
return _memoize

def mkPolyton(arghash=lambda args:args):
class Memoize(type): # Singleton is a special case of Memoize
@memoize(arghash, True) # (with arghash=lambda args:0 -> singleton)
def __call__(cls, *args):
return super(Memoize, cls).__call__(*args)
return Memoize

class Bool(int):
__metaclass__ = mkPolyton(lambda args:args and args[0] and 1 or 0)
def __repr__(self): return ('False', 'True')[self]
__str__ = __repr__

def tests(todo):
if '1' in todo:
@memoize()
def square(x): return x*x
print '[id(square(1234))...]: ten have same id:', [id(square(1234))
for x in xrange(10)].count(id(square(1234))) == 10
if '2' in todo:
F = Bool(0) # init cache with proper False value
T = Bool(1) # ditto for True value
print 'T:', T, id(T)
print 'F:', F, id(F)
print '[id(Bool(1..10))...]: ten have same id:', [id(Bool(x))
for x in xrange(1,11)].count(id(T)) == 10

if __name__ == '__main__':
import sys
tests(sys.argv[1:])
--------------------------------------------------------------------------
Result (not exactly a thorough test ;-):

[16:36] C:\pywk\ut>py24 memoize.py 1 2
[id(square(1234))...]: ten have same id: True
T: True 49271436
F: False 49271884
[id(Bool(1..10))...]: ten have same id: True

BTW, I found this page interesting:
http://c2.com/cgi/wiki?SingletonPattern


Regards,
Bengt Richter
 
J

James Stroud

Other than using modules, I thought @classmethod took care of this kind of
need:

class SingleThing:
some_values = {"fanciness":0}
def __init__(self):
raise Exception, "not enough preceding stars to be fancy enough"
@classmethod
def why_so_fancy(self):
print "why make every thing so fancy?"

I call this pattern:

"Using a Class to Be Something Single Because It Already Is Single"

or "uactbssbiais", for short.

James

--
James Stroud, Ph.D.
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com/
 
S

Steven Bethard

James said:
Other than using modules, I thought @classmethod took care of this kind of
need:

class SingleThing:
some_values = {"fanciness":0}
def __init__(self):
raise Exception, "not enough preceding stars to be fancy enough"
@classmethod
def why_so_fancy(self):
print "why make every thing so fancy?"

I call this pattern:

"Using a Class to Be Something Single Because It Already Is Single"

or "uactbssbiais", for short.

+1 VOTW (Vocabulation of the Week)

=)

STeVe
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,608
Members
45,241
Latest member
Lisa1997

Latest Threads

Top