2017-11-16 10 views
3

私はtyping.NamedTupleがPython 3.6で大好きです。しかし、多くの場合、namedtupleにハッシュ可能でない属性が含まれており、dictキーまたはsetメンバーとして使用したい場合がよくあります。 namedtupleクラスがオブジェクトID(id()の場合は__eq____hash__)を使用すると意味がある場合は、それらのメソッドをクラスに追加すると問題はありません。Typing.NamedTupleに特別なメソッド 'Mixin'を適用する方法

しかし、私は今、いくつかの場所で私のコードでは、このパターンを持っていると私は定型__eq____hash__メソッド定義を取り除きたいです。私はnamedtupleのクラスが正規のクラスではないことを知っています。私はこれをどのように動作させるかを理解することができませんでした。ここで

は、私が試したものです:

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 
""" 

は、私は私が「オブジェクトID」を必要とする各NamedTupleでこれらのメソッドを繰り返す必要はありません方法はありますか?

+0

ObjectIdentityNamedTuplクラスの_init __()メソッドはどのようにしていますか? –

+0

@DamianLattenero私は完全なコードの例を示すために私の質問を編集しました。 '__init__'はありません。私は '__new__'を追加しようとしましたが、' NamedTuple'は許されません。 –

答えて

3

NamedTupleクラス構文の魔法の源は、そのmetaclassNamedTupleMetabehind the sceneで、NamedTupleMeta.__new__ではなく、典型的な1のあなたのために新しいクラス、しかしcollections.namedtuple()で作成したクラスを作成しました。

問題があり、それは塩基クラスを無視し、新しいクラスのオブジェクトを作成するときNamedTupleMeta、あなたはTestMixinFirstのMROを確認することができ、何ObjectIdentityMixinありません:あなたは基底クラスの世話をするためにそれを拡張することができ

>>> print(TestMixinFirst.mro()) 
[<class '__main__.TestMixinFirst'>, <class 'tuple'>, <class 'object'>] 

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 
+0

素晴らしい仕事。私はこの答えを正確にマークしようとしていましたが、本当に微妙で混乱するバグを見つけました。フィールドの1つがデフォルト値を持つ場合、TestMixinが初期化された値は、名前でフィールドにアクセスする場合を除いて、どこでも使用されます。あなたの例では、 'b'の宣言を' b:int = 0'に変更すると、 'str(t1)'は 'TestMixin(a = 1、b = 2)'を返します。 't1 [1]'は2を返します。 't.b'は' 0'を返します:失敗します。 –

+0

@DamonMaria興味深い、それを後で調べます。 – georgexsh

+0

@DamonMariaは 'namedtuple()'によって作成された基底クラスに 'ns'が既に追加されているかどうかをチェックします。オーバーライドすべきではありません。 – georgexsh

関連する問題