将插槽对象复制到非插槽中

问题描述 投票:0回答:1

一些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的动态特性来实现这一目标?大概吧。

python slots
1个回答
0
投票

这是我的解决方案:

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'

Caveats

按随机顺序:

  • 某些类型可能不会减少。
  • 返回的对象是副本,而不是初始副本。
  • 班级不一样,但isinstance(d, datetime.datetime)将是True
  • 类层次结构将背叛黑客。
  • 它可能非常慢。
  • __format__有点特殊,因为你需要更改类实例,而不是因为how format works而改变绑定方法。
  • <在此插入否定评论家>。
© www.soinside.com 2019 - 2024. All rights reserved.