Date
Tags python

Intro

So for whatever reason, you want to treat your python class like a module. Why on earth would you want this?

  • Maybe you're for some reason limited to writing code in just one file, and don't want to build a proper package. It's an edge-case, but it does come up for automation scripts and the like.
  • Maybe you're working with a library that has some kind of autodiscovery feature that assumes functions will be laid out modules, but you can't justify a whole file for one small function or piece of data
  • Maybe you're planning to work with modules eventually, but for now the code is experimental, and you don't want to commit to a package layout yet until you've sketched some things out.

Problem

Let's have a more rigorous problem statement. Suppose we start with code like what you see below. This code is broken in the ways mentioned in the comments, and the goal is to find a minimal amount of extra code that makes it work.

class namespace(object):
  var = 'value1'
  def fxn1():
    return 'value2'

# passes for class & module
assert namespace.var=='value1'

# fails for class (method unbound), works for module
namespace.fxn1()=='value2'

# fails for class, passes for module
from namespace import var, fxn1
assert var=='value1'
assert fxn1()=='value2'

Solutions

First let's take a moment to appreciate that, Under the hood, classes, modules, and even instances in python are thinly veiled variations on the idea of namespaces. Given that, it's easy to accomplish turning a class into a module with metaclasses or class decorators as, long as you know about a little corner of the standard library called new.module.

Obligatory warning: What we're about to discuss is slightly evil and probably unpythonic, but assuming you have to deal with restrictions you can't reasonably get around, dynamically generated modules are still better than having disorganized code.

Python3

Python3 is missing the new module, but in python 3 this whole scenario is barely a problem at all. Just have your class extend types.ModuleType, add it to sys.modules, and you're done. For python2, extending types.ModuleType is allowed, but you'll still see a "method unbound" error and so you'll have to work harder. The solutions below probably work with python 3, but they are not necessary.

Class-decorators

Class decorators in python are just syntactic sugar analogous to function-decorators. Normally you put the decorator over your class object, and inside the decorator body you return a mutated class-object. However, there's nothing that says you must return a class object, and the code below returns a module object instead. Now the example works, and the tests will pass.

def pseudo_module(kls):
    import new, sys
    mod = new.module(kls.__name__, kls.__doc__)
    mod.__dict__.update(kls.__dict__)
    sys.modules[kls.__name__] = mod
    return mod

@pseudo_module
class namespace(object):
    var = 'value1'
    def fxn1():
      return 'value2'

assert namespace.var=='value1'
namespace.fxn1()=='value2'
from namespace import var, fxn1
assert var=='value1'
assert fxn1()=='value2'

Metaclasses

As an alternative approach, one could use metaclasses. For our use case there's no functional difference at all, but showing this this might be useful for the reader who understands decorators but not metaclasses (or vice versa)

def pseudo_module(kls_name, kls_bases, kls_dict):
    import new, sys
    mod = new.module(kls_name)
    mod.__dict__.update(kls_dict)
    sys.modules[kls_name] = mod
    return mod

class namespace(object):
    __metaclass__ = pseudo_module
    var = 'value1'
    def fxn1():
      return 'value2'

assert namespace.var=='value1'
namespace.fxn1()=='value2'
from namespace import var, fxn1
assert var=='value1'
assert fxn1()=='value2'

More Discussion

This is all more or less related to topics in metaprogramming, which is fun for the concepts in themselves, or to look into the limits of flexibility for any given language. This particular little puzzle is attractive because it's small, but it does involve enough concepts (runtime object alteration, reflection) to demonstrate a decent amount of flexibility in the core language.

Other ideas for the mad scientists in the audience:

  • What about going the other way around... could you turn a python module into a class object?
  • If so what would it mean to be "instantiate" such a class?

Trying to turn language semantics upside down and sideways like this is one of the best ways to make your understanding of them deeper.

If this kind of hackery is appealing to you, there are plenty of other things out there about class-decorators and metaclasses in python you might like to check out. Or if that seems cliche and boring why not dig into :gasp: metamodules!