Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make statement `import datetime` bind the datetime type instead of the datetime module?

After one too many times having accidentally typed import datetime when what was really needed was from datetime import datetime, I wondered whether it was possible to hack around and make the former do the latter.

That is, to recreate this behaviour (in a freshly opened interpreter session):

$ python -ic ''
>>> import datetime
>>> datetime(2016, 5, 27)
datetime.datetime(2016, 5, 27, 0, 0)

Came pretty close to faking a "callable module" below:

>>> import dt
>>> dt(2016, 5, 27)
datetime.datetime(2016, 5, 27, 0, 0)

Which was implemented like this:

# dt.py
import sys
import datetime

class CallableModule(object):
    def __init__(self, thing):
        self.thing = thing
    def __call__(self, *args, **kwargs):
        return self.thing.__call__(*args, **kwargs)

sys.modules['dt'] = CallableModule(datetime.datetime)

However it doesn't work if I try to use the filename datetime.py for the module, and I could not yet find any hack to get at the built-in datetime module when my own file was also called datetime.py.

How can we temporarily unhide a built-in or site-packages module, from within the shadowing module itself? Is there any indirect way to get at the core datetime under this situation (perhaps similar to how we can still access sys.__stdout__ even when sys.stdout has been redirected)?

Disclaimer: In no way suggesting that this is a sane idea - just interested if it's possible.

like image 827
wim Avatar asked Jan 06 '23 00:01

wim


1 Answers

Here we go:

datetime.py:

import sys
import imp
import os

path = [p for p in sys.path if p != os.path.dirname(__file__)]
f, pathname, desc = imp.find_module('datetime', path)
std_datetime = imp.load_module('datetime', f, pathname, desc)
# if this^ is renamed to datetime, everything breaks!
f.close()

class CallableModule(object):

    def __init__(self, module, fn):
        self.module = module
        self.fn = fn

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)

    def __getattr__(self, item):
        try:
            return getattr(self.fn, item)
        except AttributeError:
            return getattr(self.module, item)

sys.modules['datetime'] = CallableModule(std_datetime, std_datetime.datetime)

test.py (lives next to datetime.py):

import datetime

print(datetime(1, 2, 3))
print(datetime.timedelta(days=1))
print(datetime.now())

Run test.py, output:

0001-02-03 00:00:00
1 day, 0:00:00
2016-05-27 23:16:30.954270

It also works with from datetime import datetime, timedelta etc.

This is especially hacky and fragile and will depend on your distribution. For example, apparently it doesn't work with IPython. You must import datetime.py before the standard library module.

To understand just how weird things get with this, if the variable std_datetime (the datetime module object) is renamed to datetime, then datetime.datetime is no longer the class, but rather datetime is datetime.datetime is datetime.datetime.datetime .... If someone can explain why this happens, I'd love to hear it.

(note that the first comment below is from before I got to the final version)

like image 195
Alex Hall Avatar answered Jan 21 '23 14:01

Alex Hall