Over the past week I went through some Python code in Stackstorm (hosted in GitHub), and by doing so I learned a few cool new things. Maybe ‘new’ is not the right term, but these things were new to me and I wanted to share them with you.
The code I am referring to resides in a few places in Stackstorm, the first item:
@six.add_metaclass(abc.ABCMeta) class Access(object):
Two things here:
1. six.add_metaclass – makes Access a metaclass in a way which is compatible both with Python2 and Python3. In general, six package has a purpose of helping developers migrate their code from Python 2 to Python 3 by writing code which is compatible with both.
Hence the name: six = 2 * 3. To read more: https://pypi.python.org/pypi/six
2. abc.ABCMeta – abc is another builtin package, its purpose is to provide support for Abstract Base Classes. By making a class abstract you ensure that nobody can instantiate objects out of it.
This is useful for (at least) three purposes:
a. Maintain logic which is common to multiple classes in one place
b. force the users to implement methods which are annotated by @abc.abstractmethod (you can also force the inheriting classes to create specific class members by using the annotation @abc.abstractproperty). For more info in regards see the docs.
c. creating a singleton. I’m still not convinced about the usefulness of creating a singleton in Python (because we have modules), but assuming you want to do so, you can either create a metaclass and call its methods as ClassName.method() or extend a metaclass but not implement the abstractmethod or abstractproperty. By not implementing the abstract-methods/properties the inheriting class will become abstract as well. This technique is used in a few different places in StackStorm, if you want to see some examples, search for classes that inherit from Access.
3. This one took me a while to wrap my head around (requires a “functional programming” mind if you will):
def decorate(f): @functools.wraps(f) def callfunction(*args, **kwargs):
By calling @functools.wraps(f) we’re using a decorator inside a decorator. Not trivial…
To understand the main idea of ‘functools.wraps’, it’s easier to go through the following points/process (at least, easier for me…):
– When you decorate a function, you’re creating a new function that wraps the ‘original’ one, and return the wrapper
– The wrapper is used in order to be able to perform all kinds of tasks before/after the ‘original’ function is applied. Examples: preparing the arguments for the ‘original function’, timing how long does it take for the function to execute, add log printings before and after and etc.
– Say you have a wrapped function f, returned from a decorator, the user will then see the description (metadata) of the decorator instead of f, the original function.
– In order to avoid this confusion, @functools.wraps(f) comes into the picture: by annotating the wrapper function with @functools.wraps(f) – you’re making sure that the user of the wrapper will see the same description/metadata as the original function has. By ‘description’ I mean the function-signature (the name of the function and the names of the arguments it takes).
Hope this is helpful!