可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.