Sometimes it’s useful to have global variables, like for config or database connections. However, you don’t want to introduce side effects when you import the module (with certain exceptions).
Normally to avoid this, you would wrap your global variables in functions, maybe memoizing the return value. For example:
def get_db(): db = getattr(get_db, 'db', db_connection()) get_db.db = db return db def func1(): db = get_db() db.execute('SELECT * FROM things') def func2(): db = get_db() db.execute('SELECT * FROM other_things')
However, it gets kind of annoying having to call that function all the time when you just want to have a global variable. With Python metaclass magic, you can have that nice global variable feel without the bad side effects on import:
class Lazy(type): def __init__(cls, name, bases, dict): super(Lazy, cls).__init__(name, bases, dict) cls.instance = None def check_instance(cls): if cls.instance is None: if hasattr(cls, 'instantiate'): setattr(cls, 'instance', getattr(cls, 'instantiate')()) else: raise Exception('Must implement the instantiate class method!') def __getattr__(cls, name): cls.check_instance() return getattr(cls.instance, name) def __getitem__(cls, key): cls.check_instance() return cls.instance.__getitem__(name) def __iter__(cls): cls.check_instance() return cls.__iter__() def __contains__(self, item): cls.check_instance() return cls.__contains__(item)
The Lazy
class is a metaclass that implements the singleton design pattern. It delegates all read access to a special class variable called instance, calling the instantiate()
class method upon first access. The db
class uses this metaclass and implements the instantiate()
method.
This little bit of magic helps you keep your code clean without introducing import side effects. For more info on Python metaclasses, see Guido’s tutorial.
Comments 1
Feels like abusing, class definitions named and used as variables are confusing and unconventional. The kind of magic one might avoid on Python.
Posted 19 May 2009 at 5:19 am ¶