问题
I love the typing.NamedTuple
in Python 3.6. But there's often the case where the namedtuple
contains a non-hashable attribute and I want to use it as a dict
key or set
member. If it makes sense that a namedtuple
class uses object identity (id()
for __eq__
and __hash__
) then adding those methods to the class works fine.
However, I now have this pattern in my code in several places and I want to get rid of the boilerplate __eq__
and __hash__
method definitions. I know namedtuple
's are not regular classes and I haven't been able to figure out how to get this working.
Here's what I've tried:
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
"""
Is there a way I don't have to repeat these methods in each NamedTuple
that I need 'object identity' in?
回答1:
The magic source of NamedTuple
class syntax is its metaclass NamedTupleMeta
, behind the scene, NamedTupleMeta.__new__
created a new class for you, instead of a typical one, but a class created by collections.namedtuple()
.
The problem is, when NamedTupleMeta
creating new class object, it ignored bases classes, you could check the MRO of TestMixinFirst
, there is no ObjectIdentityMixin
:
>>> print(TestMixinFirst.mro())
[<class '__main__.TestMixinFirst'>, <class 'tuple'>, <class 'object'>]
you could extend it to take care of base 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
来源:https://stackoverflow.com/questions/47339137/how-to-apply-a-special-methods-mixin-to-a-typing-namedtuple