How can I extend Python's datetime.datetime wi

2019-03-21 03:02发布

问题:

I'm trying to extend Python's datetime.datetime class with a couple of extra methods. So, for example I'm doing:

import datetime

class DateTime(datetime.datetime):
    def millisecond(self):
        return self.microsecond/1000

but then if I do

>>> d = DateTime(2010, 07, 11, microsecond=3000)
>>> print d.millisecond()
3
>>> delta = datetime.timedelta(hours=4)
>>> newd = d + delta
>>> print newd.millisecond()
AttributeError: 'datetime.datetime' object has no attribute 'millisecond'

This is obviously because doing d + delta calls the datetime.datetime.__add__() method which returns a datetime.datetime object.

Is there any way I can make this datetime.datetime object convert to a DateTime object? Or would I have to reimplement all the operators in my DateTime subclass to return the correct type?

回答1:

In this case I'd prefer simple free-standing functions:

import datetime
def millisecond(dt):
    return dt.microsecond/1000

Mixins are possible in Python (see the comment), but I think in such cases they are superfluous.



回答2:

A bit late, but the following works:

import ctypes as c

_get_dict = c.pythonapi._PyObject_GetDictPtr
_get_dict.restype = c.POINTER(c.py_object)
_get_dict.argtypes = [c.py_object]

import datetime

def millisecond(td):
    return (td.microsecond / 1000)
d = _get_dict(datetime.datetime)[0]
d['millisecond'] = millisecond

now = datetime.datetime.now()
print now.millisecond(), now.microsecond

Basically, you use ctypes to get an editable copy of the classes dictionary, and then just put your method in. I use this only to backport functions to older python versions, which I think is fairly clean. For example:

from datetime import timedelta
try:
    timedelta.total_seconds # new in 2.7
except AttributeError:
    def total_seconds(td):
        return float(td.days * 24 * 3600 + td.seconds + td.microseconds / 1e6)
    d = _get_dict(timedelta)[0]
    d['total_seconds'] = total_seconds
    # works now in 2.4


回答3:

I tried implementing a solution using monkey-patching but ran into the error:

TypeError: can't set attributes of built-in/extension type 'datetime.datetime'

This happens with datetime.datetime.millisecond = millisecond and GvR's __metaclass__=monkeypatch_class.

Perhaps datetime.so can not be monkey-patched. If that's true, then you might want to consider this:

import datetime

class DateTime(datetime.datetime):
    @property
    def millisecond(self):
        return self.microsecond/1000.0
    def __add__(self,other):
        result=super(DateTime,self).__add__(other)
        result=DateTime(result.year,
                        result.month,
                        result.day,
                        result.hour,
                        result.minute,
                        result.second,
                        result.microsecond,
                        result.tzinfo)        
        return result
    __radd__=__add__

d = DateTime(2010, 07, 11, microsecond=3000)
print d.millisecond
# 3.0

delta = datetime.timedelta(hours=4)
newd = d + delta
print newd.millisecond
# 3.0

# This uses __radd__
newd = delta + d
print newd.millisecond
# 3.0


回答4:

unutbu was so close to what you originally asked for:

class DateTime(datetime.datetime):
    @property
    def millisecond(self):
        return self.microsecond/1000.0

datetime.datetime = DateTime

I use this pattern to implement APIs that will only show up in 3.3 while working in 3.1 or 3.2. The rest of the class keeps right on working as it ever did, with no performance loss.

Edited to add: This only works with 2.6+ for builtins, because it only works on new-style classes. Builtins were old-style until 2.6.



回答5:

At the simplet level, you can do something like this

import datetime
def millisecond(self):
    return self.microsecond/1000

datetime.datetime.millisecond = millisecond

However a dynamically inserted mixin might be neater - or just defining a function and passing it an instance of datetime.

In general monkeypatching isn't very Pythonic - its more of a Ruby approach to problems.