可以从现有日期时间实例创建的自定义日期时间子类?

Ole*_*nko 3 python inheritance datetime python-datetime

datetime.datetime给定现有datetime.datetime()实例的情况下,我需要有一个方法来轻松创建子类的实例.

说我有以下人为的例子:

class SerializableDateTime(datetime):
    def serialize(self):
        return self.strftime('%Y-%m-%d %H:%M')
Run Code Online (Sandbox Code Playgroud)

我正在使用这样的类(但有点复杂),在SQLAlchemy模型中使用; 你可以告诉SQLAlchemy的一个自定义类映射到所支持DateTime与列值TypeDecorator ; 例如:

class MyDateTime(types.TypeDecorator):
    impl = types.DateTime

    def process_bind_param(self, value, dialect):
        # from custom type to the SQLAlchemy type compatible with impl
        # a datetime subclass is fine here, no need to convert
        return value

    def process_result_value(self, value, dialect):
        # from SQLAlchemy type to custom type
        # is there a way have this work without accessing a lot of attributes each time?
        return SerializableDateTime(value)   # doesn't work
Run Code Online (Sandbox Code Playgroud)

我不能return SerializableDateTime(value)在这里使用,因为默认datetime.datetime.__new__()方法不接受datetime.datetime()实例:

>>> value = datetime.now()
>>> SerializableDateTime(value)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: an integer is required (got type datetime.datetime)
Run Code Online (Sandbox Code Playgroud)

是否有避免了复制快捷方式value.year,value.month等一路下跌到时区进入一个构造函数?

Mar*_*ers 5

虽然您可以为您的子类提供一个__new__检测单个datetime.datetime实例的方法,然后在那里进行所有复制,但实际上我只是给类一个类方法来处理这种情况,因此您的SQLAlchemy代码如下所示:

return SerializableDateTime.from_datetime(value)
Run Code Online (Sandbox Code Playgroud)

我们可以利用类已经实现的pickle支持datetime.datetime(); types实现了__reduce_ex__钩子(通常构建在更高级别的方法上__getnewargs__),对于datetime.datetime()实例,这个钩子只返回datetime.datetime类型和args元组,这意味着只要你有一个具有相同内部状态的子类,我们就可以创建一个新的副本.通过将args元组应用回新类型来实现相同的状态.该__reduce_ex__方法可以通过pickle协议改变输出,但只要您传入,就pickle.HIGHEST_PROTOCOL可以保证获得完全支持的值范围.

所述args元组由一个或两个的值,第二个是时区:

>>> from pickle import HIGHEST_PROTOCOL
>>> value = datetime.now()
>>> value.__reduce_ex__(HIGHEST_PROTOCOL)
(<class 'datetime.datetime'>, (b'\x07\xe2\n\x1f\x12\x06\x05\rd\x8f',))
>>> datetime.utcnow().astimezone(timezone.utc).__reduce_ex__(value.__reduce_ex__(HIGHEST_PROTOCOL))
(<class 'datetime.datetime'>, (b'\x07\xe2\n\x1f\x12\x08\x14\n\xccH', datetime.timezone.utc))
Run Code Online (Sandbox Code Playgroud)

在该第一值args元组是一个bytes表示对象(除了时区)的所有属性值,以及用于构造datetime接受相同的字节值(加上可选时区):

>>> datetime(b'\x07\xe2\n\x1f\x12\x06\x05\rd\x8f') == value
True
Run Code Online (Sandbox Code Playgroud)

由于您的子类接受相同的参数,您可以使用args元组来创建副本; 我们可以使用第一个值来防止未来Python版本中的更改,断言它仍然是我们的父类:

from pickle import HIGHEST_PROTOCOL

class SerializableDateTime(datetime):
    @classmethod
    def from_datetime(cls, dt):
        """Create a SerializableDateTime instance from a datetime.datetime object"""
        # (ab)use datetime pickle support to copy state across
        factory, args = dt.__reduce_ex__(HIGHEST_PROTOCOL)
        assert issubclass(cls, factory)
        return cls(*args)

    def serialize(self):
        return self.strftime('%Y-%m-%d %H:%M')
Run Code Online (Sandbox Code Playgroud)

这允许您创建子类的实例作为副本:

>>> SerializableDateTime.from_datetime(datetime.now())
SerializableDateTime(2018, 10, 31, 18, 13, 2, 617875)
>>> SerializableDateTime.from_datetime(datetime.utcnow().astimezone(timezone.utc))
SerializableDateTime(2018, 10, 31, 18, 13, 22, 782185, tzinfo=datetime.timezone.utc)
Run Code Online (Sandbox Code Playgroud)

虽然使用pickle __reduce_ex__hook看起来有点hackish,但这也是用于使用模块创建datetime.datetime实例副本的实际协议,并且通过使用您可以确保在您使用的Python版本中复制所有相关状态.copy__reduce_ex__(HIGHEST_PROTOCOL)