誰でも 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の特性に従って、表現されるときの出力の順序は、オブジェクトを構築するときのパラメーターリストの順序と一致する必要があります。
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__
ベースのインスタンスよりも少ないインスタンスがメモリを占有するクラスを生成できます。これは、参照サイクルを持つことを意図していない属性値を持つインスタンスにとって重要です。数百万のインスタンスを作成する必要がある場合、メモリ使用量を削減するのに役立ちます。これが実例です 例 。
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)
この質問に対する答えはノーのようです。
以下はかなり近いですが、技術的には変更できません。これは、更新された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スロット
最新の 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の意図を理解できなかったのでしょうか?
このタスクの非常に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)
以下は、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)
動的な型作成でこれを実装しましょう:
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"])
そして、はい、それはタイプ作成でリストされたフィールドの順序を保持します。
Namedtuplesと同様の動作が必要であるが、可変である場合 namedlist
可変であるためには、がTupleにならないことに注意してください。
タプルは定義上不変です。
ただし、ドット表記で属性にアクセスできるディクショナリサブクラスを作成できます。
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}
パフォーマンスの重要性がほとんどない場合、次のような愚かなハックを使用できます。
from collection import namedtuple
Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])