如何将特殊方法 'Mixin' 应用于 typing.NamedTuple
How to apply a special methods 'Mixin' to a typing.NamedTuple
我喜欢 Python 3.6 中的 typing.NamedTuple
。但通常情况下 namedtuple
包含不可散列的属性,我想将其用作 dict
键或 set
成员。如果 namedtuple
class 使用对象标识(id()
用于 __eq__
和 __hash__
)是有意义的,那么将这些方法添加到 class 是可行的很好。
但是,我现在在我的代码中的几个地方都有这种模式,我想摆脱样板 __eq__
和 __hash__
方法定义。我知道 namedtuple
不是常规的 classes 并且我一直无法弄清楚如何让它工作。
这是我尝试过的方法:
from typing import NamedTuple
class ObjectIdentityMixin:
def __eq__(self, other):
return self is other
def __hash__(self):
return id(self)
class TestMixinFirst(ObjectIdentityMixin, NamedTuple):
a: int
print(TestMixinFirst(1) == TestMixinFirst(1)) # Prints True, so not using my __eq__
class TestMixinSecond(NamedTuple, ObjectIdentityMixin):
b: int
print(TestMixinSecond(2) == TestMixinSecond(2)) # Prints True as well
class ObjectIdentityNamedTuple(NamedTuple):
def __eq__(self, other):
return self is other
def __hash__(self):
return id(self)
class TestSuperclass(ObjectIdentityNamedTuple):
c: int
TestSuperclass(3)
"""
Traceback (most recent call last):
File "test.py", line 30, in <module>
TestSuperclass(3)
TypeError: __new__() takes 1 positional argument but 2 were given
"""
有什么方法可以让我不必在每个 'object identity' 中需要的 NamedTuple
中重复这些方法?
NamedTuple
class 语法的神奇来源是它的 metaclass NamedTupleMeta
, behind the scene,NamedTupleMeta.__new__
为您创建一个新的 class,而不是典型的,但是 class 由 collections.namedtuple()
创建。
问题是,当NamedTupleMeta
创建新的class对象时,它忽略了基础classes,你可以检查TestMixinFirst
的MRO,没有ObjectIdentityMixin
:
>>> print(TestMixinFirst.mro())
[<class '__main__.TestMixinFirst'>, <class 'tuple'>, <class 'object'>]
你必须扩展 NamedTupleMeta
来处理基础 classes:
import typing
class NamedTupleMetaEx(typing.NamedTupleMeta):
def __new__(cls, typename, bases, ns):
cls_obj = super().__new__(cls, typename+'_nm_base', bases, ns)
bases = bases + (cls_obj,)
return type(typename, bases, {})
class TestMixin(ObjectIdentityMixin, metaclass=NamedTupleMetaEx):
a: int
b: int = 10
t1 = TestMixin(1, 2)
t2 = TestMixin(1, 2)
t3 = TestMixin(1)
assert hash(t1) != hash(t2)
assert not (t1 == t2)
assert t3.b == 10
我喜欢 Python 3.6 中的 typing.NamedTuple
。但通常情况下 namedtuple
包含不可散列的属性,我想将其用作 dict
键或 set
成员。如果 namedtuple
class 使用对象标识(id()
用于 __eq__
和 __hash__
)是有意义的,那么将这些方法添加到 class 是可行的很好。
但是,我现在在我的代码中的几个地方都有这种模式,我想摆脱样板 __eq__
和 __hash__
方法定义。我知道 namedtuple
不是常规的 classes 并且我一直无法弄清楚如何让它工作。
这是我尝试过的方法:
from typing import NamedTuple
class ObjectIdentityMixin:
def __eq__(self, other):
return self is other
def __hash__(self):
return id(self)
class TestMixinFirst(ObjectIdentityMixin, NamedTuple):
a: int
print(TestMixinFirst(1) == TestMixinFirst(1)) # Prints True, so not using my __eq__
class TestMixinSecond(NamedTuple, ObjectIdentityMixin):
b: int
print(TestMixinSecond(2) == TestMixinSecond(2)) # Prints True as well
class ObjectIdentityNamedTuple(NamedTuple):
def __eq__(self, other):
return self is other
def __hash__(self):
return id(self)
class TestSuperclass(ObjectIdentityNamedTuple):
c: int
TestSuperclass(3)
"""
Traceback (most recent call last):
File "test.py", line 30, in <module>
TestSuperclass(3)
TypeError: __new__() takes 1 positional argument but 2 were given
"""
有什么方法可以让我不必在每个 'object identity' 中需要的 NamedTuple
中重复这些方法?
NamedTuple
class 语法的神奇来源是它的 metaclass NamedTupleMeta
, behind the scene,NamedTupleMeta.__new__
为您创建一个新的 class,而不是典型的,但是 class 由 collections.namedtuple()
创建。
问题是,当NamedTupleMeta
创建新的class对象时,它忽略了基础classes,你可以检查TestMixinFirst
的MRO,没有ObjectIdentityMixin
:
>>> print(TestMixinFirst.mro())
[<class '__main__.TestMixinFirst'>, <class 'tuple'>, <class 'object'>]
你必须扩展 NamedTupleMeta
来处理基础 classes:
import typing
class NamedTupleMetaEx(typing.NamedTupleMeta):
def __new__(cls, typename, bases, ns):
cls_obj = super().__new__(cls, typename+'_nm_base', bases, ns)
bases = bases + (cls_obj,)
return type(typename, bases, {})
class TestMixin(ObjectIdentityMixin, metaclass=NamedTupleMetaEx):
a: int
b: int = 10
t1 = TestMixin(1, 2)
t2 = TestMixin(1, 2)
t3 = TestMixin(1)
assert hash(t1) != hash(t2)
assert not (t1 == t2)
assert t3.b == 10