Recently I changed jobs, making some professional changes that I’ve wanted to make for some time:
Using Linux has been such a great change. It feels like clay that I can shape closely around my workflow - the more tricks I discover/apply, the more productive I become. However, that’s not the topic of this post; my new work language is - Python.
I’ve written a lot of Python over the last three months, but just using a language doesn’t bestow fluency in and of itself. It takes some time to absorb the idioms, but more than that, you actually need problems to solve that require you to delve into the language facilities.
I was recently thinking about such a problem. This situation is not a great technical quandary, but it did motivate me into a deeper look at Python and in particular, Metaclasses.
My current contract involves a large scale, multi-part solution for acquiring, parsing and aggregating web data. The business domain is such that several data items must be one of a sizable list of constant values. At present these are stored in lists of strings. This works OK for determining whether a value is one of the list members, but means using string literals throughout - it requires memorisation/lookup of values, makes it hard to take advantage of intellisense and is error prone. A Pythonic alternative could be to use a module containing all the constant values, but this makes testing for membership inelegant. A static (only) class, with constant members and some reflection-esque way of testing a value against the members is what we need.
So in summary the requirements are:
Preventing instantiation is easily done by throwing an exception in __new__.
Customising access to members is possible via the Python special methods __getattr__, __setattr__ and __delattr__ described here, but these are instance methods. How do we recruit these for static members? I had an inkling that this could be done with a Metaclass based on my knowledge of them in Ruby. Although the concept is different in Python, I was right.
Finally, testing for membership in the collection is done via another special Python member, __dict__.
All in all, this is done with cogent brevity as follows.
# The metaclass allowing us to deal dynamically with getters and setters.
class ConstCollectionMetaclass(type):
def __setattr__(self, name, value):
self.__dict__[name] = value
def __getattr__(self, name):
if name in self.__dict__:
return self.__dict__[name]
else:
raise NameError('Constant not found in collection: ' + name)
def contains(self, value):
return value in self.__dict__.values()
# The base implementation recruiting the metaclass and preventing instantiation.
class ConstCollection(object):
__metaclass__ = ConstCollectionMetaclass
def __new__(cls, *args, **kwargs):
raise TypeError("Class 'ConstCollection' and derivations cannot be instantiated.")
# An example implementation.
class FootballCodes(ConstCollection):
UK_FOOTBALL = 'Soccer'
AU_FOOTBALL = 'Australian Rules'
US_FOOTBALL = 'Gridiron'
# Check that it does what we expect.
if __name__ == "__main__":
print FootballCodes.UK_FOOTBALL # Soccer
print FootballCodes.contains('Australian Rules') # True
print FootballCodes.contains('Rugby Union') # False
try:
nz_football = FootballCodes.NZ_FOOTBALL
except NameError, e:
print e # Constant not found in collection: NZ_FOOTBALL
try:
FootballCodes.NZ_FOOTBALL = 'Rugby Union'
except TypeError, e:
print e # 'dictproxy' object does not support item assignment
try:
football_codes = FootballCodes()
except TypeError, e:
print e # Class 'ConstCollection' and derivations cannot be instantiated.
Now I’m not sure this is strictly Pythonic, but it serves more as an intellectual exercise.