将槽对象复制到非槽中
Copy a slot object into a non-slot
一些 python 标准 类 是插槽,例如 datetime.datetime
。这不是我可以改变的,很多库都期望 datetime
object.
我想更改现有 datetime
对象的默认 __format__
方法,但不幸的是,由于这是一个插槽 类,它是被禁止的:
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 的动态特性来实现这一点?我想是的。
这是我的解决方案:
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)
它基本上做了以下事情:
- 测试对象是否已经有
__dict__
。既然如此,就无计可施了。
- 根据提供的对象类型创建一个新类型。这个新类型不是插槽class,尽量模仿基本类型。
- 像
copy.copy
中那样减少提供的对象,但为简单起见仅支持 __reduce_ex__(4)
。
- 修改缩减版本以使用新创建的类型。
- 使用修改后的简化版本取消选中新对象。
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'
注意事项
顺序随机:
- 有些类型可能不会减少。
- 返回的对象是副本,不是初始对象。
- 和class不一样,但是
isinstance(d, datetime.datetime)
会是True
。
- class 等级制度会背叛黑客。
- 它可能非常慢。
__format__
有点特殊,因为需要改变class实例,而不是因为how format works. 绑定的方法
- <在此处插入您的负面评论>。
一些 python 标准 类 是插槽,例如 datetime.datetime
。这不是我可以改变的,很多库都期望 datetime
object.
我想更改现有 datetime
对象的默认 __format__
方法,但不幸的是,由于这是一个插槽 类,它是被禁止的:
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 的动态特性来实现这一点?我想是的。
这是我的解决方案:
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)
它基本上做了以下事情:
- 测试对象是否已经有
__dict__
。既然如此,就无计可施了。 - 根据提供的对象类型创建一个新类型。这个新类型不是插槽class,尽量模仿基本类型。
- 像
copy.copy
中那样减少提供的对象,但为简单起见仅支持__reduce_ex__(4)
。 - 修改缩减版本以使用新创建的类型。
- 使用修改后的简化版本取消选中新对象。
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'
注意事项
顺序随机:
- 有些类型可能不会减少。
- 返回的对象是副本,不是初始对象。
- 和class不一样,但是
isinstance(d, datetime.datetime)
会是True
。 - class 等级制度会背叛黑客。
- 它可能非常慢。
__format__
有点特殊,因为需要改变class实例,而不是因为how format works. 绑定的方法
- <在此处插入您的负面评论>。