可以从现有datetime实例创建的自定义datetime子类?

时间:2018-10-31 17:21:42

标签: python datetime inheritance python-datetime

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

说我有以下人为的例子:

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

我正在使用类似这样的类(但要复杂一些),以用于SQLAlchemy模型;您可以告诉SQLAlchemy使用TypeDecorator class将自定义类映射到受支持的DateTime列值;例如:

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

在这里我不能使用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)

是否存在避免将value.yearvalue.month等一直复制到时区并复制到构造函数中的快捷方式?

1 个答案:

答案 0 :(得分:5)

尽管您可以给您的子类一个__new__方法,该方法可以检测到一个datetime.datetime实例,然后在那里进行所有复制,但实际上我会给该类一个类方法来处理这种情况,因此您可以SQLAlchemy代码如下:

return SerializableDateTime.from_datetime(value)

我们可以利用pickle类已经实现的datetime.datetime()支持;类型实现__reduce_ex__ hook(通常建立在类似__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))

args元组中的第一个值是一个bytes值,代表对象的所有属性(时区除外),datetime的构造函数接受相同的字节值(加上可选的时区):

>>> datetime(b'\x07\xe2\n\x1f\x12\x06\x05\rd\x8f') == value
True

由于子类接受相同的参数,因此可以使用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')

这使您可以将子类的实例创建为副本:

>>> 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)

尽管使用腌制__reduce_ex__钩子看起来有些黑,但这是用于创建copy moduledatetime.datetime实例副本以及使用{{1 }},请确保所有相关状态都被复制,无论您使用的是哪个Python版本。