web-dev-qa-db-ja.com

namedtupleのサブクラスに属性を設定できません

this または this のように見えますが、関連するスレッドですが、まだ理解できていません:)

namedtupleのサブクラスを作成し、さまざまな方法でオブジェクトを構築できるようにさまざまな初期化子を提供しようとしています。例えば:

>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
...     __slots__ = ()
...     def __init__(self, obj) : # Initialize a C instance by copying values from obj
...         self.x = obj.a
...         self.y = obj.b
...     def __init__(self, x, y) : # Initialize a C instance from the parameters
...         self.x = x
...         self.y = y

ただし、それは機能しません。

>>> c = C(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __init__
AttributeError: can't set attribute

いろいろ調べてから(たとえば、 this threadを参照)、初期化子の代わりにコンストラクターを使用しようとしました。

>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
...     __slots__ = ()
...     def __new__(cls, obj) :
...       self = super(C, cls).__new__(cls, obj.a, obj.b)
...     def __new__(cls, x, y) :
...       self = super(C, cls).__new__(cls, x, y)

オブジェクトを構築しているように見えましたが、その属性を読み取ることができません:

>>> c = C(1,2)
>>> c.x, c.y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'x'

私はここでどこが間違っていますか?複数のコンストラクターまたは初期化子を持つサブクラスを作成するにはどうすればよいですか?

26
Jens

名前付きタプルは不変なので、___init___初期化子でそれらを操作することはできません。唯一のオプションは___new___メソッドをオーバーライドすることです:

_class C(namedtuple('C', 'x, y')):
    __slots__ = ()
    def __new__(cls, obj):
        return super(C, cls).__new__(cls, obj.x, obj.y)
_

___new___は新しいインスタンスのファクトリメソッドであるため、新しく作成されたインスタンスをreturnする必要があることに注意してください。 ___new___メソッドでreturnを使用しない場合、デフォルトの戻り値はNoneであり、エラーが発生します。

xおよびy属性を持つオブジェクトを使用したデモ:

_>>> class C(namedtuple('C', 'x, y')):
...     __slots__ = ()
...     def __new__(cls, obj):
...         return super(C, cls).__new__(cls, obj.x, obj.y)
... 
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)
_

Pythonはメソッドのオーバーロードをサポートしていません。通常、オプションのキーワード引数または追加のクラスメソッドをファクトリメソッドとして使用します。

たとえば、 datetime module には、標準のコンストラクタに適合しないオブジェクトを作成できるようにするためのファクトリメソッドがいくつかあります。 datetime.datetime.fromtimestamp() は、単一の数値から_datetime.datetime_インスタンスを作成します datetime.datetime.fromordinal() ;ただし、番号はさまざまな方法で解釈されます。

可変引数をサポートする場合は、次を実行します。

_class C(namedtuple('C', 'x, y')):
    __slots__ = ()

    def __new__(cls, x, y=None):
        if y is None:
            # assume attributes
            x, y = x.x, x.y
        return super(C, cls).__new__(cls, x, y)
_

ここで、yはオプションの引数で、呼び出し元から提供されない場合のデフォルトはNoneです。

_>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
_

クラスメソッドを使用する代替方法は次のとおりです。

_class C(namedtuple('C', 'x, y')):
    @classmethod
    def from_attributes(cls, obj):
        return cls(obj.x, obj.y)
_

現在、2つのファクトリーメソッドがあります。 1つのデフォルトと1つの名前:

_>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
_
31
Martijn Pieters

_replace メソッド

from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)
3
Yonatan Simson

2つのことがあります。1つは、私が知る限り、ここでnamedtupleからあまり多くを得ていないことです。したがって、通常のクラスに切り替える必要があります。また、あなたはオーバーロードすることはできません

第二に、あなたの問題に役立つかもしれない他の可能性:

工場設計パターン -コンストラクターにさまざまなパラメーターを配置する代わりに、さまざまな種類のパラメーターを取り、オブジェクトの外部で適切な引数を使用してコンストラクターを呼び出すクラスを用意します。 recordtype -変更可能な名前付きタプル。デフォルトを許可しますが、元の方法でサブクラスを記述することもできます。 bunch -名前付きタプルではありませんが、いくぶん任意のオブジェクトを作成できます。

3
Corley Brigman

名前付きタプルの属性を変更する回避策があります。

import collections

def updateTuple(NamedTuple,nameOfNamedTuple):
    ## Convert namedtuple to an ordered dictionary, which can be updated
    NamedTuple_asdict = NamedTuple._asdict()

    ## Make changes to the required named attributes
    NamedTuple_asdict['path']= 'www.google.com'

    ## reconstruct the namedtuple using the updated ordered dictionary
    updated_NamedTuple = collections.namedtuple(nameOfNamedTuple, NamedTuple_asdict.keys())(**NamedTuple_asdict)

    return updated_NamedTuple

Tuple = collections.namedtuple("Tuple", "path")
NamedTuple = Tuple(path='www.yahoo.com')
NamedTuple = updateTuple(NamedTuple, "Tuple")
1
chahuja