Python3.5 对象和 json.dumps() 输出

Python3.5 object and json.dumps() output

我写了一个 class,它允许我将天数(整数)添加到日期(字符串 %Y-%m-%d)。此 class 的对象需要 JSON 可序列化。

以整数形式向我的对象添加天数按预期工作。但是 json.dumps(obj) returns 我的原始对象的信息太多(“2016-03-23 15:57:47.926362”)。 为什么?我需要如何修改 class 以获取“2016-03-23”?请参阅下面的示例。

代码:

from datetime import datetime, timedelta
import json

class Day(str):
    def __init__(self, _datetime):
        self.day = _datetime

    def __str__(self):
        return self.day.date().isoformat()

    def __repr__(self):
        return "%s" % self.day.date().isoformat()

    def __add__(self, day):
        new_day = self.day + timedelta(days=day)
        return Day(new_day).__str__()

    def __sub__(self, day):
        new_day = self.day - timedelta(days=day)
        return Day(new_day).__str__()


if __name__ == "__main__":
    today = Day(datetime.today())
    print(today)               # 2016-03-23
    print(json.dumps(today))   # "2016-03-23 15:57:47.926362"
    print(today+1)             # 2016-03-24
    print(json.dumps(today+1)) # "2016-03-24"
    print(today-1)             # 2016-03-22
    print(json.dumps(today-1)) # "2016-03-22"

更新。这是我给感兴趣的人的最终代码:

from datetime import datetime, timedelta
import json


class Day(str):
    def __init__(self, datetime_obj):
        self.day = datetime_obj

    def __new__(self, datetime):
        return str.__new__(Day, datetime.date().isoformat())

    def __add__(self, day):
        new_day = self.day + timedelta(days=day)
        return Day(new_day)

    def __sub__(self, day):
        new_day = self.day - timedelta(days=day)
        return Day(new_day)


if __name__ == "__main__":
    today = Day(datetime.today())
    print(type(today))
    print(today)  # 2016-03-23
    print(json.dumps(today))  # "2016-03-23"
    print(today + 1)  # 2016-03-24
    print(json.dumps(today + 1))  # "2016-03-24"
    print(today - 1)  # 2016-03-22
    print(json.dumps(today - 1))  # "2016-03-22"
    print(json.dumps(dict(today=today))) # {"today": "2016-03-23"}
    print(json.dumps(dict(next_year=today+365))) # {"next_year": "2017-03-23"}
    print(json.dumps(dict(last_year=today-366))) # {"last_year": "2015-03-23"}

酷!让我们一起去吧。您正在查看:

print(json.dumps(today))   # "2016-03-23 15:57:47.926362"

因为 somewhere 在编码过程中,在决定如何序列化传递给它的内容时,json.dumps 在您的对象上调用 isinstance(..., str)。这个 returns True 和你的对象像这个字符串一样被序列化了。

但是 "2016-03-23 15:57:47.926362" 值从何而来?

当您调用 day = Day(datetime_obj) 时,会发生两件事:

  • __new__ is called 实例化 对象。您没有提供 __new__ 方法,因此使用 str.__new__
  • __init__ 被调用以初始化 对象。

所以 day = Day(datetime_obj) 有效地转换为:

day = str.__new__(Day, datetime_obj)

对于 json.dumps,您的对象将是 str,但 str 的值设置为 datetime_obj 的默认字符串表示形式。这恰好是您看到的完整格式。内建,伙计!

我玩过这个,看来如果你滚动你自己的 __new__(这是一个稍微令人兴奋的领域,小心行事)拦截 str.__new__ 调用,你 ~~应该~~没事:

class Day(str):
    def __new__(self, datetime):
        return str.__new__(Day, datetime.date().isoformat())

但是如果整件事都着火了,你没有听到我的消息。

PS would be 继承 JSONEncoder 的正确方法。但是其中的乐趣为零。

PS2 天啊,我在 2.7 上测试过这个。我可能完全不在那里,如果是,请给我一个 "you tried" 徽章。

json.dumps(today) 行为的原因并不像乍看起来那么明显。要理解这个问题,您应该能够回答两个问题:

  • 包含时间的字符串值从哪里来?
  • 为什么 Day.__str__ 不被 json 编码器调用?应该吗?

这里有一些先决条件:

  1. datetime.today() 方法类似于 datetime.now() -- 它包括当前时间(小时、分钟等)。您可以 使用 date.today(),只获取日期。

  2. str 在 Python 中创建不可变对象;它的值在您未覆盖的 __new__ 方法中设置,因此默认转换 str(datetime.today()) 用于将 Day 的值初始化为字符串。在您的情况下,它会创建包含日期和时间的字符串值。您可以 覆盖 __new__,以获得不同的字符串值 :

    def __new__(cls, _datetime):
        return str.__new__(cls, _datetime.date())
    
  3. Day 是一个 str subclass 因此它的实例被编码为 JSON strings

  4. str 方法 return str 对象而不是相应的 subclass 对象,除非你覆盖它们例如:

    >>> class S(str):
    ...    def duplicate(self):
    ...        return S(self * 2)
    ...
    >>> s = S('abc')
    >>> s.duplicate().duplicate()
    'abcabcabcabc'
    >>> s.upper().duplicate()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'str' object has no attribute 'duplicate'
    

    s.upper() returns str 对象而不是 S 并且下面的 .duplicate() 调用失败。

在您的例子中,要创建相应的 JSON 字符串,json.dumps(today)today 对象执行操作(re.sub()json.encode.encode_basestring() 中调用)将其值用作字符串,即,问题是 re.sub()encode_basestring() 都没有在 str subclass 实例上调用 __str__() 方法。即使 encode_basestring(s)return '"' + s + '"' 一样简单;结果是一样的:'"' + today return 是一个 str 对象并且 Day.__str__ 没有被调用。

我不知道 re 模块是否应该在接受 isinstance(obj, str) 的函数中调用 str(obj)。或者 json.encode.encode_basestring() 是否应该这样做(或两者都不做)。

如果无法修复Dayclass;您可以 修补 json.encode.encode_basestring() 以调用 str(obj),以获得 str 子类型实例 的理想 JSON 表示( 如果你想通过__str__()方法得到值return——暂且不考虑覆盖是否明智__str__() 在第一个 str 子 class 上 ):

import json

for suffix in ['', '_ascii']:
    function_name = 'encode_basestring' + suffix
    orig_function = getattr(json.encoder, function_name)
    setattr(json.encoder, function_name, lambda s,_e=orig_function: _e(str(s)))

相关 Python 问题:Cannot override JSON encoding of basic type subclasses