我需要有一个方法来方便地创建一个datetime.datetime
子类的实例,给定现有的datetime.datetime()
实例。
假设我有以下人为的例子:
class SerializableDateTime(datetime):
def serialize(self):
return self.strftime('%Y-%m-%d %H:%M')
我正在使用这样一个类(但有点复杂),以便在sqlalchemy模型中使用;您可以告诉sqlalchemy将自定义类映射到带有
DateTime
class的受支持的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
我不能在这里使用
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.year
、value.month
等一直复制到时区的构造函数中? 最佳答案
尽管你可以给你的子类一个__new__
方法来检测一个datetime.datetime
实例,然后在那里进行所有的复制,但我实际上会给这个类一个class method来处理这种情况,所以你的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
就保证得到支持的值的完整范围。元组由一个或两个值组成,第二个值是时区:
>>> 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
的构造函数接受相同的字节值(加上可选时区):>>> 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)
虽然使用pickle
bytes
钩子看起来有点老套,但这是用于创建datetime
module实例副本的实际协议,通过使用args
可以确保复制所有相关状态,无论您使用的是什么python版本。