Thu 21 Jun 2007
I know what you’re thinking: “what the hell? You can’t subclass modules!” Conventional wisdom == wrong.
import os
class MyOS(os):
__metaclass__ = ModuleMeta
def lstat(self, arg):
return 6
def rmdir(self, arg):
raise self.error("No such file or directory: %r" % arg)
Notice that we’re apparently subclassing a module. The metaclass will allow us to override whichever of the module’s functions we desire, leaving the others intact.
class ModuleMeta(type):
def __new__(cls, name, bases, d):
d["__getattr__"] = lambda x, y: getattr(bases[0], y)
return type.__new__(cls, name, (object,), d)
This is the little beauty that makes the whole thing possible. Here, we stick a custom __getattr__() function into the class’s namespace, then replace the incoming bases tuple with our own. The bases we were passed will contain a module, and that will cause the runtime to complain if the module reaches type.__new__().
Some client code:
os = MyOS()
print os.lstat("foo")
print os.times()
os.rmdir("foo")
Our custom os-alike provides its own rmdir() and lstat() functions while using the times() function from the real os module. This works in Python 2.3, 2.4 and 2.5.
I see requests for this fairly regularly when people are wanting to stub out certain functions in a module for testing purposes. Of course, the easy way to do this isn’t to subclass the module at all: just create a class that does what you want.
class MyOS:
def lstat(self, arg):
return 6
def rmdir(self, arg):
raise self.error("No such file or directory: %r" % arg)
def __getattr__(self, attr):
return getattr(os, attr)
No fuss, no muss, and it’s fully equivalent to the above magic metaclass incantations. I’ll talk more about this in a future post.
June 21st, 2007 at 21:20
Interesting, but I don’t really see the point when you can simply do:
import os
os.lstat = my_lstat_func
os.rmdir = my_rmdir_func
What advantage do you get from this subclassing approach that you wouldn’t get from simple shadowing?
June 22nd, 2007 at 01:02
Oh, there’s no benefit whatsoever to this particular approach; it’s just a stupid Python trick.
The advantage to creating your own os module rather than monkeypatching the real one is that you have more complete control over what resources are available to the code under test.
June 22nd, 2007 at 17:10
Monkeypatching alters the original everywhere it’s used in the program. Subclassing means you could sandbox just particular parts of code. Said sandboxing would of course only be protection against accident, not malice, but it would still be quite useful.
June 23rd, 2007 at 14:32
I’ve been looking for ways to do this for a while, and never thought of this elegant trick. Nice!
–titus
p.s. no, I have no good reason for doing it. does that ever stop anyone?