Attaching Attributes to Functions
Disclaimer: I can’t tell if I like this behavior or not, but I’m leaning towards it being an awesome way to avoid using classes while still gaining the benefit of singleton-like behavior.
def init_something():
if not hasattr(init_something, 'val'):
val = 'WHATEVER'
init_something.val = val
return init_something.val
Let’s dig into what this code is doing.
How It Works
First, notice that we are using the function’s name, init_something inside it’s definition. I can then use this name to check if any attributes are attached to it. Being able to reference a function by it’s name is similar to using self in a class, except we won’t be using a class.
Next, we’ll call our cached value val. You probably want a more descriptive name in practice, but this works for now. If init_something hasn’t initialized val, we will give it a value and then attach it to the function. Every check after the first one will already have val ready and waiting to go.
So… does it work? Let’s add a print statement and see.
>>> def init_something():
... if not hasattr(init_something, 'val'):
... val = 'WHATEVER'
... print 'Initializing value'
... init_something.val = val
... return init_something.val
...
>>> init_something()
Initializing value
'WHATEVER'
>>> init_something()
'WHATEVER'
>>> init_something()
'WHATEVER'
Sweet! We only see it initialize the value on the first call
Doing It With Classes
I find it neat because the alternative is to use a class, which is heavier. This same behavior is achieved by using a class and implementing `__new__()`. This is different from `__init__()` because `__new__()` is the function responsible for creating `self`, which gets passed into `__init__()`.
More info: http://docs.python.org/reference/datamodel.html#object.__new__
That means we can implement `__new__()` such that it will only create a new instance in the case that one doesn’t already exist. That looks like below.
>>> class SomeClass(object):
... def __new__(cls, *a, **kw):
... if not hasattr(cls, 'val'):
... print 'Initializing value'
... cls.val = 'WHATEVER'
... return cls.val
...
>>> sc = SomeClass()
Initializing value
>>> sc = SomeClass()
>>> sc = SomeClass()
Notice that using it even seems to behave the same. The only difference is that one is a function and one is class and when I have to choose, I will choose the function.