To answer the more generic question of how to create constrained instances, it depends on the constraint. Both you examples above are a sort of "singletons", although the second example is a variation where you can have many instances of one class, but you will have only one per input value.
These can both done by overriding the class' __new__
method, so that the class creates the instances if it hasn't been created already, and both returns it, and stores it as an attribute on the class (as has been suggested above). However, a slightly less hackish way is to use metaclasses. These are classes that change the behaviour of classes, and singletons is a great example of when to use metaclasses. And the great thing about this is that you can reuse metaclasses. By creating a Singleton metaclass, you can then use this metaclass for all Singletons you have.
A nice Python example in on Wikipedia: http://en.wikipedia.org/wiki/Singleton_pattern#Python
Here is a variation that will create a different instance depending on parameters:
(It's not perfect. If you pass in a parameter which is a dict, it will fail, for example. But it's a start):
# Notice how you subclass not from object, but from type. You are in other words
# creating a new type of type.
class SingletonPerValue(type):
def __init__(cls, name, bases, dict):
super(SingletonPerValue, cls).__init__(name, bases, dict)
# Here we store the created instances later.
cls.instances = {}
def __call__(cls, *args, **kw):
# We make a tuple out of all parameters. This is so we can use it as a key
# This will fail if you send in unhasheable parameters.
params = args + tuple(kw.items())
# Check in cls.instances if this combination of parameter has been used:
if params not in cls.instances:
# No, this is a new combination of parameters. Create a new instance,
# and store it in the dictionary:
cls.instances[params] = super(SingletonPerValue, cls).__call__(*args, **kw)
return cls.instances[params]
class MyClass(object):
# Say that this class should use a specific metaclass:
__metaclass__ = SingletonPerValue
def __init__(self, value):
self.value = value
print 1, MyClass(1)
print 2, MyClass(2)
print 2, MyClass(2)
print 2, MyClass(2)
print 3, MyClass(3)
But there are other constraints in Python than instantiation. Many of them can be done with metaclasses. Others have shortcuts, here is a class that only allows you to set the attributes 'items' and 'fruit', for example.
class Constrained(object):
__slots__ = ['items', 'fruit']
con = Constrained()
con.items = 6
con.fruit = "Banana"
con.yummy = True
If you want restrictions on attributes, but not quite these strong, you can override __getattr__, __setattr__ and __delattr__
to make many fantastic and horrid things happen. :-) There are also packages out there that let you set constraints on attributes, etc.