web-dev-qa-db-ja.com

PythonにTupleという名前の可変が存在しますか?

誰でも namedtuple を修正したり、代替オブジェクトを提供して、変更可能なオブジェクトで動作するようにできますか?

主に読みやすさのために、これを行うnamedtupleに似たものが欲しいです:

from Camelot import namedgroup

Point = namedgroup('Point', ['x', 'y'])
p = Point(0, 0)
p.x = 10

>>> p
Point(x=10, y=0)

>>> p.x *= 10
Point(x=100, y=0)

結果のオブジェクトをピクルスできる必要があります。また、名前付きTupleの特性に従って、表現されるときの出力の順序は、オブジェクトを構築するときのパラメーターリストの順序と一致する必要があります。

95
Alexander

collections.namedtupleに変更可能な代替手段があります- recordclass

namedtupleと同じAPIとメモリフットプリントを持ち、割り当てをサポートします(同様に高速になるはずです)。例えば:

from recordclass import recordclass

Point = recordclass('Point', 'x y')

>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

python 3.6以降recordclass(0.5以降)がtypehintsをサポートする場合:

from recordclass import recordclass, RecordClass

class Point(RecordClass):
   x: int
   y: int

>>> Point.__annotations__
{'x':int, 'y':int}
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

より完全な があります(パフォーマンスの比較も含まれます)。

0.9以降、recordclassライブラリは別のバリアントを提供します-recordclass.structclassファクトリー関数。 __slots__ベースのインスタンスよりも少ないインスタンスがメモリを占有するクラスを生成できます。これは、参照サイクルを持つことを意図していない属性値を持つインスタンスにとって重要です。数百万のインスタンスを作成する必要がある場合、メモリ使用量を削減するのに役立ちます。これが実例です

108
intellimath

types.SimpleNamespace はPython 3.3で導入され、要求された要件をサポートします。

from types import SimpleNamespace
t = SimpleNamespace(foo='bar')
t.ham = 'spam'
print(t)
namespace(foo='bar', ham='spam')
print(t.foo)
'bar'
import pickle
with open('/tmp/pickle', 'wb') as f:
    pickle.dump(t, f)
23
funky-future

この質問に対する答えはノーのようです。

以下はかなり近いですが、技術的には変更できません。これは、更新されたx値で新しいnamedtuple()インスタンスを作成しています:

Point = namedtuple('Point', ['x', 'y'])
p = Point(0, 0)
p = p._replace(x=10) 

一方、__slots__を使用して、クラスインスタンスの属性を頻繁に更新するのに適した単純なクラスを作成できます。

class Point:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y

この答えに追加するには、__slots__がここでの使用に適していると思います。これは、多くのクラスインスタンスを作成するときにメモリ効率が良いからです。唯一の欠点は、新しいクラス属性を作成できないことです。

メモリ効率を示す関連するスレッドが1つあります- Dictionary vs Object-これはより効率的で、なぜですか?

このスレッドの答えの引用された内容は、__slots__の方がメモリ効率が高い理由を簡潔に説明したものです- Pythonスロット

21
kennes

最新の namedlist 1.7は、Python 2.7とPython 3.5の両方ですべてのテストに合格します2016年1月11日現在これは純粋なpython実装ですrecordclassはCです拡張。もちろん、C拡張が優先されるかどうかは要件によって異なります。

テスト(ただし、以下の注も参照):

from __future__ import print_function
import pickle
import sys
from namedlist import namedlist

Point = namedlist('Point', 'x y')
p = Point(x=1, y=2)

print('1. Mutation of field values')
p.x *= 10
p.y += 10
print('p: {}, {}\n'.format(p.x, p.y))

print('2. String')
print('p: {}\n'.format(p))

print('3. Representation')
print(repr(p), '\n')

print('4. Sizeof')
print('size of p:', sys.getsizeof(p), '\n')

print('5. Access by name of field')
print('p: {}, {}\n'.format(p.x, p.y))

print('6. Access by index')
print('p: {}, {}\n'.format(p[0], p[1]))

print('7. Iterative unpacking')
x, y = p
print('p: {}, {}\n'.format(x, y))

print('8. Iteration')
print('p: {}\n'.format([v for v in p]))

print('9. Ordered Dict')
print('p: {}\n'.format(p._asdict()))

print('10. Inplace replacement (update?)')
p._update(x=100, y=200)
print('p: {}\n'.format(p))

print('11. Pickle and Unpickle')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print('Pickled successfully\n')

print('12. Fields\n')
print('p: {}\n'.format(p._fields))

print('13. Slots')
print('p: {}\n'.format(p.__slots__))

Python 2.7での出力

 1。フィールド値の突然変異
 p:10、12 
 
 2。文字列
 p:Point(x = 10、y = 12)
 
 3。表現
 Point(x = 10、y = 12)
 
 4。 Sizeof 
 pのサイズ:64 
 
 5。フィールドの名前によるアクセス
 p:10、12 
 
 6。インデックスによるアクセス
 p:10、12 
 
 7。反復解凍
 p:10、12 
 
 8。反復
 p:[10、12] 
 
 9。 Ordered Dict 
 p:OrderedDict([( 'x'、10)、( 'y'、12)])
 
 10。インプレース置換(更新?)
 p:Point(x = 100、y = 200)
 
 11。ピクルスとアンピクル
ピクルスに成功しました
 
 12。フィールド
 p:( 'x'、 'y')
 
 13。スロット
 p:( 'x'、 'y')

Python 3.5との唯一の違いは、namedlistが小さくなり、サイズが56になることです(Python 2.7レポート64)。

インプレース置換のためにテスト10を変更したことに注意してください。namedlistには_replace()メソッドがあり、これは浅いコピーしてください。標準ライブラリのnamedtupleは同じように動作するため、それは私にとって完全に理にかなっています。 _replace()メソッドのセマンティクスを変更すると混乱を招くでしょう。私の意見では、インプレース更新には_update()メソッドを使用する必要があります。それとも、テスト10の意図を理解できなかったのでしょうか?

20
Ali

このタスクの非常にPython的な代替として、Python-3.7以降では、通常のクラス定義を使用し、他のクラス機能もサポートしているため、可変dataclassesのように動作するNamedTupleモジュールを使用できます。

PEP-0557から:

これらは非常に異なるメカニズムを使用しますが、データクラスは「デフォルトを持つ可変の名前付きタプル」と考えることができます。データクラスは通常のクラス定義構文を使用するため、継承、メタクラス、ドキュメント文字列、ユーザー定義メソッド、クラスファクトリ、およびその他のPythonクラス機能を自由に使用できます。

PEP 526 、「変数注釈の構文」で定義されている型注釈を持つ変数のクラス定義を検査するクラスデコレーターが提供されます。このドキュメントでは、そのような変数はフィールドと呼ばれます。これらのフィールドを使用して、デコレーターは生成されたメソッド定義をクラスに追加して、インスタンスの初期化、repr、比較メソッド、およびオプションで Specification セクションで説明されているその他のメソッドをサポートします。このようなクラスはデータクラスと呼ばれますが、実際にはクラスには特別なものはありません。デコレータは生成されたメソッドをクラスに追加し、与えられた同じクラスを返します。

この機能は PEP-0557 で導入されており、提供されているドキュメントリンクで詳細を読むことができます。

例:

In [20]: from dataclasses import dataclass

In [21]: @dataclass
    ...: class InventoryItem:
    ...:     '''Class for keeping track of an item in inventory.'''
    ...:     name: str
    ...:     unit_price: float
    ...:     quantity_on_hand: int = 0
    ...: 
    ...:     def total_cost(self) -> float:
    ...:         return self.unit_price * self.quantity_on_hand
    ...:    

デモ:

In [23]: II = InventoryItem('bisc', 2000)

In [24]: II
Out[24]: InventoryItem(name='bisc', unit_price=2000, quantity_on_hand=0)

In [25]: II.name = 'choco'

In [26]: II.name
Out[26]: 'choco'

In [27]: 

In [27]: II.unit_price *= 3

In [28]: II.unit_price
Out[28]: 6000

In [29]: II
Out[29]: InventoryItem(name='choco', unit_price=6000, quantity_on_hand=0)
10
Kasrâmvd

以下は、Python 3の適切なソリューションです。__slots__およびSequence抽象基本クラスを使用する最小クラス。派手なエラー検出などは行いませんが、動作し、ほとんど変更可能なタプルのように動作します(タイプチェックを除く)。

from collections import Sequence

class NamedMutableSequence(Sequence):
    __slots__ = ()

    def __init__(self, *a, **kw):
        slots = self.__slots__
        for k in slots:
            setattr(self, k, kw.get(k))

        if a:
            for k, v in Zip(slots, a):
                setattr(self, k, v)

    def __str__(self):
        clsname = self.__class__.__name__
        values = ', '.join('%s=%r' % (k, getattr(self, k))
                           for k in self.__slots__)
        return '%s(%s)' % (clsname, values)

    __repr__ = __str__

    def __getitem__(self, item):
        return getattr(self, self.__slots__[item])

    def __setitem__(self, item, value):
        return setattr(self, self.__slots__[item], value)

    def __len__(self):
        return len(self.__slots__)

class Point(NamedMutableSequence):
    __slots__ = ('x', 'y')

例:

>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)

必要に応じて、クラスを作成するメソッドを使用することもできます(ただし、明示的なクラスを使用すると、より透明になります)。

def namedgroup(name, members):
    if isinstance(members, str):
        members = members.split()
    members = Tuple(members)
    return type(name, (NamedMutableSequence,), {'__slots__': members})

例:

>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)

Python 2では、少し調整する必要があります。 Sequenceから継承すると、クラスには__dict__があります になり、__slots__が機能しなくなります。

Python 2の解決策は、Sequenceではなくobjectを継承することです。 isinstance(Point, Sequence) == Trueが必要な場合は、NamedMutableSequenceを基本クラスとしてSequenceに登録する必要があります。

Sequence.register(NamedMutableSequence)
6
Antti Haapala

動的な型作成でこれを実装しましょう:

import copy
def namedgroup(typename, fieldnames):

    def init(self, **kwargs): 
        attrs = {k: None for k in self._attrs_}
        for k in kwargs:
            if k in self._attrs_:
                attrs[k] = kwargs[k]
            else:
                raise AttributeError('Invalid Field')
        self.__dict__.update(attrs)

    def getattribute(self, attr):
        if attr.startswith("_") or attr in self._attrs_:
            return object.__getattribute__(self, attr)
        else:
            raise AttributeError('Invalid Field')

    def setattr(self, attr, value):
        if attr in self._attrs_:
            object.__setattr__(self, attr, value)
        else:
            raise AttributeError('Invalid Field')

    def rep(self):
         d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
         return self._typename_ + '(' + ', '.join(d) + ')'

    def iterate(self):
        for x in self._attrs_:
            yield self.__dict__[x]
        raise StopIteration()

    def setitem(self, *args, **kwargs):
        return self.__dict__.__setitem__(*args, **kwargs)

    def getitem(self, *args, **kwargs):
        return self.__dict__.__getitem__(*args, **kwargs)

    attrs = {"__init__": init,
                "__setattr__": setattr,
                "__getattribute__": getattribute,
                "_attrs_": copy.deepcopy(fieldnames),
                "_typename_": str(typename),
                "__str__": rep,
                "__repr__": rep,
                "__len__": lambda self: len(fieldnames),
                "__iter__": iterate,
                "__setitem__": setitem,
                "__getitem__": getitem,
                }

    return type(typename, (object,), attrs)

これにより、属性の有効性が確認されてから、操作の続行が許可されます。

これは漬物ですか?はい(もしあなたが以下をするならば)

>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True

定義は名前空間に存在する必要があり、pickleがそれを見つけるのに十分な長さで存在する必要があります。したがって、これをパッケージに定義すると、動作するはずです。

Point = namedgroup("Point", ["x", "y"])

次のことを行うか、定義を一時的にすると(関数の終了時にスコープから外れるなど)、Pickleは失敗します。

some_point = namedgroup("Point", ["x", "y"])

そして、はい、それはタイプ作成でリストされたフィールドの順序を保持します。

3
MadMan2064

Namedtuplesと同様の動作が必要であるが、可変である場合 namedlist

可変であるためには、がTupleにならないことに注意してください。

2
agomcas

タプルは定義上不変です。

ただし、ドット表記で属性にアクセスできるディクショナリサブクラスを作成できます。

In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
:    def __getattr__(self, name):
:        return self[name]
:
:    def __setattr__(self, name, value):
:        self[name] = value
:--

In [2]: test = AttrDict()

In [3]: test.a = 1

In [4]: test.b = True

In [5]: test
Out[5]: {'a': 1, 'b': True}
1
Roland Smith

パフォーマンスの重要性がほとんどない場合、次のような愚かなハックを使用できます。

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])
0
Srg