Пользовательский подкласс datetime, который может быть создан из существующего экземпляра datetime? - PullRequest
0 голосов
/ 31 октября 2018

Мне нужен метод для простого создания экземпляра подкласса datetime.datetime с учетом существующего экземпляра datetime.datetime().

Скажем, у меня есть следующий надуманный пример:

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

Я использую такой класс (но немного более сложный) для использования в модели SQLAlchemy; Вы можете указать SQLAlchemy сопоставить пользовательский класс с поддерживаемым значением столбца DateTime с TypeDecorator class ; e.g.:

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 и т. Д. Вплоть до часового пояса в конструктор?

1 Ответ

0 голосов
/ 31 октября 2018

Хотя вы можете дать своему подклассу метод __new__, который обнаруживает один экземпляр datetime.datetime, а затем выполняет все копирование там, я бы фактически дал классу метод класса, просто чтобы обработать этот случай, чтобы ваш код SQLAlchemy выглядел так как:

return SerializableDateTime.from_datetime(value)

Мы можем использовать поддержку pickle, которую класс datetime.datetime() уже реализует; типы реализуют хук __reduce_ex__ (обычно на основе методов более высокого уровня, таких как __getnewargs__), а для экземпляров datetime.datetime() этот хук возвращает только тип datetime.datetime и args, что означает, что, если у вас есть подкласс с тем же внутренним состоянием, мы можем создать новую копию с тем же состоянием, применив кортеж args к вашему новому типу. Метод __reduce_ex__ может варьировать выходные данные в соответствии с протоколом маринования, но если вы передадите 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)

Хотя использование хука pickle __reduce_ex__ может показаться несколько хакерским, именно этот протокол используется для создания копий datetime.datetime экземпляров с модулем copy , а также с использованием __reduce_ex__(HIGHEST_PROTOCOL) Вы гарантируете, что все соответствующие состояния копируются независимо от используемой версии Python.

...