在Python中,在混合类型数的操作中,较窄的类型为widened to that of the other,例如int
+ float
→float
:
In [57]: 3 + 0.1
Out[57]: 3.1
但对于datetime.date
,我们有datetime.date
+ datetime.timedelta
→datetime.date
,不 datetime.datetime
:
In [58]: datetime.date(2013, 1, 1) + datetime.timedelta(seconds=42)
Out[58]: datetime.date(2013, 1, 1)
为什么扩大推理应用于数字,而不适用于date
/ datetime
/ timedelta
?
(背景:我正在编写一个文件格式的阅读例程,其中一个字段是年份,一个字段是一年中的一个字段,一个字段是毫秒 - 从午夜开始。当然,简单明了解决方案是datetime.datetime(2013, 1, 1, 0, 0, 0) + datetime.timedelta(seconds=42)
,但人们可以同样地认为应该将3 + 0.1
重写为3.0 + 0.1
)
答案 0 :(得分:7)
行为是documented:
date2 在
timedelta.days > 0
时向前移动,如果在timedelta.days < 0
则向后移动。之后date2 - date1 == timedelta.days
。timedelta.seconds
和timedelta.microseconds
会被忽略。
(我的重点。由于在Python 2.3中添加了date
个对象,因此此行为有remained unchanged。)
我无法找到任何关于为什么模块设计如此的证据。当然,像你这样的用例你想要代表与一天开始的午夜相对应的时间点。在这些情况下,必须来回转换是令人讨厌的。但是还有其他一些用例需要代表一整天(而不仅仅是那天的某个时间点),在这种情况下,您不希望在添加timedeltas时意外地结束部分日期。
Chris Withers建议在issue 3249中更改行为,但Tim Peters指出:
对记录的始终工作的这种行为的不兼容的改变不太可能被接受。
如果你想要一个行为类似于datetime.date
的对象,但算术运算会返回datetime.datetime
个对象,那么写一个对象应该不会太难:
from datetime import date, datetime, time, timedelta
def _part_day(t):
"""Return True if t is a timedelta object that does not consist of
whole days.
"""
return isinstance(t, timedelta) and (t.seconds or t.microseconds)
class mydate(date):
"""Subclass of datetime.date where arithmetic operations with a
timedelta object return a datetime.datetime object unless the
timedelta object consists of whole days.
"""
def datetime(self):
"""Return datetime corresponding to the midnight at start of this
date.
"""
return datetime.combine(self, time())
def __add__(self, other):
if _part_day(other):
return self.datetime() + other
else:
return super().__add__(other)
__radd__ = __add__
def __sub__(self, other):
if _part_day(other):
return self.datetime() - other
else:
return super().__sub__(other)
(这是未经测试的,但从这里开始工作应该不难。)
答案 1 :(得分:6)
timedelta
对象不会存储有关日期或时间的任何信息。 (小时/分钟/秒/微秒数为0的事实可能只是巧合!)
因此,假设我们有一个人只想操纵日期,忽略时间,她会做my_new_date = my_old_date + timedelta(days=1)
之类的事情。她发现my_new_date
现在是datetime
对象而不是date
对象,我感到非常惊讶并且可能很恼火。
答案 2 :(得分:5)
日期不是datetime的子类; datetime是复合类型,将<VirtualHost *:80>
DocumentRoot /var/www/html/public
# ...
<Directory /var/www/html/public>
# ...
</Directory>
# ...
</VirtualHost>
和date
对象合并为一个。你不能决定从这里time
的操作中产生一个复合类型; date
组件不是日期的一小部分。
另一方面,Python numeric hierarchy将整数定义为一种专用浮点数(time
是numbers.Integral
的间接子类),因此整数和浮点数之间的混合操作结果在正在生产的基本类型中。并且numbers.Real
不是复合类型,值的小数部分没有单独的类型。
如果要从日期操作中生成复合类型,则必须是显式的(并且显式优于隐式)。自己添加时间组件:
float
其中datetime.combine(yourdate, time.min) + yourdelta
可以通过yourdate
解析您的年份以及每年的日期输入来生成。
备选方案(根据date.strptime('%Y %j')
值生成datetime
对象有时,或始终)需要程序员解包日期如果这是他们所期望的那样,那就再次组成。
答案 3 :(得分:3)
可能的技术原因是Python中的内置类型不会从其运算符返回子类,即date.__add__
不会返回datetime
。一致的行为需要date
和datetime
可以互换(它们不是)。
date + timedelta
行为并且不会发生变化。如果您希望datetime
作为结果;从日期datetime
创建d
:
dt = datetime(d.year, d.month, d.day)
从技术上讲,date.__add__
可以将工作委托给timedelta.__radd__
。 timedelta
分别存储days
因此,它是否简单有效地确定它是否代表整数天,即如果我们想从date
或datetime
获取date + timedelta
{1}}(这并不意味着我们应该这样做。)
问题是1
和1.0
在这种情况下是timedelta(1)
相同,即,如果我们允许date + timedelta
返回datetime
那么它如果我们只考虑类型,则应该为所有值返回datetime
。
根据结果,int + int
返回int
或long
时有一个先例,即具有相同类型的操作可能会返回不同类型的值,具体取决于输入值。虽然date
和datetime
的互换性不如int
和long
那么多。
date + timedelta
为其他人date
和timedelta
返回datetime
会造成混淆,除非我们也引入date(y,m,d) == datetime(y,m,d)
(例如1 == 1.0
)或来自the related Python issue mentioned by @Gareth Rees的date.today() < datetime.now()
(如1 < 1.1
)。 datetime
作为一个子类建议这条路线虽然我听到一个论点,认为datetime
是date
子类是错误的。
在dateutil
包中实现了所需的行为:
>>> from datetime import date
>>> from dateutil.relativedelta import relativedelta
>>> date.today() + relativedelta(days=1)
datetime.date(2016, 5, 12)
>>> date.today() + relativedelta(days=1, seconds=1)
datetime.datetime(2016, 5, 12, 0, 0, 1)
time() + timedelta
无效,因此combine()
对我没有帮助。
combine()
有效datetime.combine(d, time()) + timedelta_obj
。但是,您可以将其写为:datetime(d.year, d.month, d.day) + timedelta_obj
。
当然,简单明确的解决方案是
datetime(2013, 1, 1, 0, 0, 0) + timedelta(seconds=42)
,但也可以将3 + 0.1
重写为3.0 + 0.1
)
int
+ float
始终为float
:
>>> 3 + 1.0
4.0
>>> 3 + 1
4
与1.0
和1
不同; type(timedelta(1.0)) == type(timedelta(1))
(相同类型除外)。
我正在为一个文件格式编写一个阅读例程,其中一个字段是年份,一个字段是一年中的一个字段,一个字段是毫秒 - 从午夜开始。
dt = datetime(year, 1, 1) + timedelta(days=day_of_year - 1, milliseconds=ms)
答案 4 :(得分:-3)
当你将timedelta与日期相加时,python只会查找timedelta的days属性。因此,如果添加42秒,那么这一天将为0并且不会影响您的日期。