How to apply a special methods 'Mixin' to a typing.NamedTuple

蹲街弑〆低调 提交于 2020-05-13 02:28:47

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!