Скопируйте объект слота в не-слот - PullRequest
0 голосов
/ 29 марта 2019

Некоторые стандартные классы Python являются слотами, например datetime.datetime. Это не то, что я могу изменить, и многие библиотеки ожидают datetime объект.

Я хотел изменить метод __format__ по умолчанию для существующего объекта datetime, но, к сожалению, поскольку это классы слотов, это запрещено:

In [10]: import datetime

In [11]: datetime.datetime.now().__format__ = lambda s, f: ''
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-11-c98141136d9d> in <module>()
----> 1 datetime.datetime.now().__format__ = lambda s, f: ''

AttributeError: 'datetime.datetime' object attribute '__format__' is read-only

Можно ли злоупотреблять динамической природой python для достижения этой цели? Я так думаю.

1 Ответ

0 голосов
/ 29 марта 2019

Вот мое решение:

def make_extendable(o):
    """
    Return an object that can be extended via its __dict__
    If it is a slot, the object type is copied and the object is pickled through
    this new type, before returning it.

    If there is already a __dict__, then the object is returned.
    """
    if getattr(o, "__dict__", None) is not None:
        return o

    # Now for fun
    # Don't take care of immutable types or constant for now
    import copy
    import copyreg

    cls = o.__class__
    new_cls = type(cls.__name__, (cls,), {"__module__": cls.__module__})
    # Support only Python >= 3.4
    pick = o.__reduce_ex__(4)
    if pick[0] == cls:
        # This is the case for datetime objects
        pick = (new_cls, *pick[1:])
    elif pick[0] in (copyreg.__newobj__, copyreg.__newobj_ex__):
        # Now the second item in pick is (cls, )
        # It should be rare though, it's only for slots
        pick = (pick[0], (new_cls,), *pick[2:])
    else:
        return ValueError(f"Unable to extend {o} of type {type(o)}")

    # Build new type
    return copy._reconstruct(o, None, *pick)

Это в основном делает следующее:

  1. Проверка, если объект уже имеет __dict__.В этом случае делать нечего.
  2. Создать новый тип на основе предоставленного типа объекта.Этот новый тип не является классом слотов, и постарайтесь максимально имитировать базовый тип.
  3. Уменьшите предоставленный объект, как сделано в copy.copy, но для простоты поддерживает только __reduce_ex__(4).
  4. Измените сокращенную версию для использования вновь созданного типа.
  5. Отмените выбор нового объекта, используя измененную уменьшенную версию.

Результат для datetime:

In [13]: d = make_extendable(datetime.datetime.now())

In [14]: d
Out[14]: datetime(2019, 3, 29, 11, 24, 23, 285875)

In [15]: d.__class__.__mro__
Out[15]: (datetime.datetime, datetime.datetime, datetime.date, object)

In [16]: d.__str__ = lambda: 'Hello, world'

In [17]: d.__str__()
Out[17]: 'Hello, world'

Предостережения

В случайном порядке:

  • Некоторые типы не могут быть уменьшены.
  • Возвращенный объект является копией, а не исходной.
  • Класс не тот же, но isinstance(d, datetime.datetime) будет True.
  • Иерархия классов выдаст хак.
  • Это может быть невероятно медленно.
  • __format__ немного особенный, потому что вам нужно изменить экземпляр класса, а не связанный метод из-за , как работает формат .
  • .
...