私はこれを必要としたことは一度もありませんが、Pythonで不変オブジェクトを作成するのは少し難しいかもしれないと思いました。 __setattr__
で属性を設定することさえできないため、単に __init__
をオーバーライドすることはできません。タプルのサブクラス化はうまくいくトリックです。
class Immutable(Tuple):
def __new__(cls, a, b):
return Tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
しかし、その後、self[0]
およびself[1]
を介してa
およびb
変数にアクセスできますが、これは面倒です。
これはPure Pythonで可能ですか?そうでない場合、C拡張でどのようにすればよいですか?
(Python 3でのみ機能する回答は受け入れられます)。
更新:
ですから、Tupleをサブクラス化することは、Pure Pythonで行う方法です。これは、[0]
、[1]
などによってデータにアクセスする追加の可能性を除いて、うまく機能します。 、geititem
やsetattribute
などを実装しないことで、非常に簡単になると思います。しかし、自分でやるのではなく、怠け者なので賞金を提供します。 :)
私が考えていたさらに別のソリューション:元のコードと同じ動作を得る最も簡単な方法は
Immutable = collections.namedtuple("Immutable", ["a", "b"])
[0]
などを介して属性にアクセスできるという問題は解決しませんが、少なくともかなり短く、pickle
およびcopy
と互換性があるという追加の利点があります。
namedtuple
は この回答 で説明したものと同様のタイプを作成します。つまり、Tuple
から派生し、__slots__
を使用します。 Python 2.6以降で利用可能です。
これを行う最も簡単な方法は、__slots__
を使用することです。
class A(object):
__slots__ = []
A
のインスタンスは、属性を設定できないため、不変です。
クラスインスタンスにデータを含める場合、これをTuple
からの派生と組み合わせることができます。
from operator import itemgetter
class Point(Tuple):
__slots__ = []
def __new__(cls, x, y):
return Tuple.__new__(cls, (x, y))
x = property(itemgetter(0))
y = property(itemgetter(1))
p = Point(2, 3)
p.x
# 2
p.y
# 3
Edit:インデックス作成を削除したい場合は、__getitem__()
をオーバーライドできます:
class Point(Tuple):
__slots__ = []
def __new__(cls, x, y):
return Tuple.__new__(cls, (x, y))
@property
def x(self):
return Tuple.__getitem__(self, 0)
@property
def y(self):
return Tuple.__getitem__(self, 1)
def __getitem__(self, item):
raise TypeError
この場合、プロパティにoperator.itemgetter
を使用できないことに注意してください。これはPoint.__getitem__()
ではなくTuple.__getitem__()
に依存するためです。さらに、これによってTuple.__getitem__(p, 0)
の使用が妨げられることはありませんが、これがどのように問題を構成するのか想像することはできません。
不変オブジェクトを作成する「正しい」方法は、C拡張機能を書くことだとは思いません。 Pythonは通常、ライブラリの実装者とライブラリユーザーが 大人の同意 であることに依存しており、インターフェースを実際に強制する代わりに、インターフェースをドキュメントに明確に記述する必要があります。これが、__setattr__()
を呼び出してオーバーライドされたobject.__setattr__()
を回避する可能性を考慮しない理由です。誰かがこれを行うと、それは彼女自身のリスクになります。
..Cで「適切に」行う方法.
Cython を使用して、Pythonの拡張タイプを作成できます。
cdef class Immutable:
cdef readonly object a, b
cdef object __weakref__ # enable weak referencing support
def __init__(self, a, b):
self.a, self.b = a, b
Python 2.xと3の両方で動作します。
# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable
o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2
try: o.a = 3
except AttributeError:
pass
else:
assert 0, 'attribute must be readonly'
try: o[1]
except TypeError:
pass
else:
assert 0, 'indexing must not be supported'
try: o.c = 1
except AttributeError:
pass
else:
assert 0, 'no new attributes are allowed'
o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []
o.b.append(3) # attribute may contain mutable object
assert o.b == [3]
try: o.c
except AttributeError:
pass
else:
assert 0, 'no c attribute'
o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3
try: del o.b
except AttributeError:
pass
else:
assert 0, "can't delete attribute"
d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']
o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3
try: object.__setattr__(o, 'a', 1)
except AttributeError:
pass
else:
assert 0, 'attributes are readonly'
try: object.__setattr__(o, 'c', 1)
except AttributeError:
pass
else:
assert 0, 'no new attributes'
try: Immutable(1,c=3)
except TypeError:
pass
else:
assert 0, 'accept only a,b keywords'
for kwd in [dict(a=1), dict(b=2)]:
try: Immutable(**kwd)
except TypeError:
pass
else:
assert 0, 'Immutable requires exactly 2 arguments'
インデックスのサポートを気にしない場合は、 collections.namedtuple
@ Sven Marnach が推奨します:
Immutable = collections.namedtuple("Immutable", "a b")
別のアイデアは、__setattr__
を完全に禁止し、コンストラクターでobject.__setattr__
を使用することです。
class Point(object):
def __init__(self, x, y):
object.__setattr__(self, "x", x)
object.__setattr__(self, "y", y)
def __setattr__(self, *args):
raise TypeError
def __delattr__(self, *args):
raise TypeError
もちろんobject.__setattr__(p, "x", 3)
を使用してPoint
インスタンスp
を変更できますが、元の実装には同じ問題があります(Immutable
インスタンスでTuple.__setattr__(i, "x", 42)
を試してください) 。
元の実装にも同じトリックを適用できます。__getitem__()
を取り除き、プロパティ関数でTuple.__getitem__()
を使用します。
@immutable
and をオーバーライドする__setattr__
デコレーターを作成して、__slots__
を空のリストに変更し、それで__init__
メソッドをデコレートできます。
編集:OPが述べたように、__slots__
属性を変更しても、変更ではなく新しい属性の作成のみが防止されます。
Edit2:実装は次のとおりです。
Edit3:__slots__
を使用すると、このコードが壊れます。なぜなら、オブジェクトの__dict__
の作成が停止されるためです。私は代替を探しています。
Edit4:さて、それで終わりです。それはハックですが、演習として機能します:-)
class immutable(object):
def __init__(self, immutable_params):
self.immutable_params = immutable_params
def __call__(self, new):
params = self.immutable_params
def __set_if_unset__(self, name, value):
if name in self.__dict__:
raise Exception("Attribute %s has already been set" % name)
if not name in params:
raise Exception("Cannot create atribute %s" % name)
self.__dict__[name] = value;
def __new__(cls, *args, **kws):
cls.__setattr__ = __set_if_unset__
return super(cls.__class__, cls).__new__(cls, *args, **kws)
return __new__
class Point(object):
@immutable(['x', 'y'])
def __new__(): pass
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z
Tupleまたはnamedtupleを使用することを除いて、完全に可能だとは思いません。いずれにしても、__setattr__()
をオーバーライドすると、ユーザーはobject.__setattr__()
を直接呼び出すことでいつでもバイパスできます。 __setattr__
に依存するソリューションは、動作しないことが保証されています。
以下は、何らかのタプルを使用せずに取得できる最も近いものです。
class Immutable:
__slots__ = ['a', 'b']
def __init__(self, a, b):
object.__setattr__(self, 'a', a)
object.__setattr__(self, 'b', b)
def __setattr__(self, *ignored):
raise NotImplementedError
__delattr__ = __setattr__
しかし、あなたが十分に努力すると壊れます:
>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2
しかし、Svenによるnamedtuple
の使用は、完全に不変です。
更新
質問はCで適切に行う方法を尋ねるように更新されているため、Cythonで適切に行う方法に関する私の答えは次のとおりです。
最初のimmutable.pyx
:
cdef class Immutable:
cdef object _a, _b
def __init__(self, a, b):
self._a = a
self._b = b
property a:
def __get__(self):
return self._a
property b:
def __get__(self):
return self._b
def __repr__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
そして、それをコンパイルするためのsetup.py
(コマンドsetup.py build_ext --inplace
を使用:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [Extension("immutable", ["immutable.pyx"])]
setup(
name = 'Immutable object',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)
それから試してみる:
>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>
エレガントソリューションです:
class Immutable(object):
def __setattr__(self, key, value):
if not hasattr(self, key):
super().__setattr__(key, value)
else:
raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))
このクラスから継承し、コンストラクターでフィールドを初期化すると、すべて設定されます。
他の優れた答えに加えて、python 3.4(または3.3)のメソッドを追加したいと思います。この回答は、この質問に対する以前のいくつかの回答に基づいています。
python 3.4では、プロパティをセッターなしで使用して、変更できないクラスメンバーを作成できます。 (以前のバージョンでは、セッターなしでプロパティに割り当てることが可能でした。)
class A:
__slots__=['_A__a']
def __init__(self, aValue):
self.__a=aValue
@property
def a(self):
return self.__a
次のように使用できます。
instance=A("constant")
print (instance.a)
"constant"
を出力します
ただし、instance.a=10
を呼び出すと、以下が発生します。
AttributeError: can't set attribute
説明:セッターのないプロパティはpython 3.4のごく最近の機能です(そして3.3と思います)。そのようなプロパティに割り当てようとすると、エラーが発生します。スロットを使用して、メンバー変数を__A_a
(__a
)に制限します。
問題:_A__a
への割り当ては引き続き可能です(instance._A__a=2
)。しかし、プライベート変数に割り当てる場合、それはあなた自身の責任です...
この答え ただし、__slots__
の使用は推奨されません。属性の作成を防ぐために他の方法を使用することをお勧めします。
振る舞いを持つオブジェクトに興味がある場合、namedtupleはalmostソリューションです。
Namedtuple documentation の下部で説明されているように、namedtupleから独自のクラスを派生できます。その後、必要な動作を追加できます。
例( documentation から直接取得したコード):
class Point(namedtuple('Point', 'x y')):
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
for p in Point(3, 4), Point(14, 5/7):
print(p)
これにより、次の結果が得られます。
Point: x= 3.000 y= 4.000 hypot= 5.000
Point: x=14.000 y= 0.714 hypot=14.018
このアプローチは、Python 3とPython 2.7の両方で機能します(IronPythonでもテスト済み)。
唯一の欠点は、継承ツリーが少し奇妙だということです。しかし、これは通常あなたが遊ぶものではありません。
__setattr__
をオーバーライドし、呼び出し元が__init__
の場合にセットを許可することにより、不変のクラスを作成しました。
import inspect
class Immutable(object):
def __setattr__(self, name, value):
if inspect.stack()[2][3] != "__init__":
raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
object.__setattr__(self, name, value)
誰でも___init__
でオブジェクトを変更できるため、これではまだ十分ではありませんが、アイデアは得られます。
Python 3.7では、クラスで @dataclass
デコレーター を使用でき、構造体のように不変になります!ただし、クラスに __hash__()
メソッドを追加してもしなくてもかまいません。見積もり:
hash()は組み込みのhash()によって使用され、オブジェクトが辞書やセットなどのハッシュされたコレクションに追加されるときに使用されます。 hash()を持つことは、クラスのインスタンスが不変であることを意味します。可変性は、プログラマの意図、eq()の存在と動作、およびeqの値と凍結フラグに依存する複雑なプロパティですdataclass()デコレータ。
デフォルトでは、dataclass()は、安全でない限り、暗黙的にhash()メソッドを追加しません。既存の明示的に定義されたhash()メソッドの追加も変更も行いません。クラス属性の設定hash= Noneは、hash()ドキュメント。
hash()が明示的に定義されていない場合、またはNoneに設定されている場合、dataclass()は暗黙的なhash()メソッド。推奨されていませんが、dataclass()にhash()メソッドをunsafe_hash = Trueで強制的に作成させることができます。これは、クラスが論理的に不変であるにもかかわらず、変更可能である場合に当てはまります。これは特殊なユースケースであり、慎重に検討する必要があります。
ここに、上記のリンクされたドキュメントの例:
@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
この方法でobject.__setattr__
の動作が停止することはありませんが、私はまだ便利だと感じています。
class A(object):
def __new__(cls, children, *args, **kwargs):
self = super(A, cls).__new__(cls)
self._frozen = False # allow mutation from here to end of __init__
# other stuff you need to do in __new__ goes here
return self
def __init__(self, *args, **kwargs):
super(A, self).__init__()
self._frozen = True # prevent future mutation
def __setattr__(self, name, value):
# need to special case setting _frozen.
if name != '_frozen' and self._frozen:
raise TypeError('Instances are immutable.')
else:
super(A, self).__setattr__(name, value)
def __delattr__(self, name):
if self._frozen:
raise TypeError('Instances are immutable.')
else:
super(A, self).__delattr__(name)
ユースケースに応じて、さらに多くのもの(__setitem__
など)をオーバーライドする必要がある場合があります。
少し前にこれが必要だったので、Pythonパッケージを作成することにしました。初期バージョンは現在PyPIにあります:
$ pip install immutable
使用するには:
>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1
完全なドキュメントはこちら: https://github.com/theengineear/immutable
これが役に立てば、説明したように名前付きタプルをラップしますが、インスタンス化はもっと簡単になります。
次のImmutable
クラスから継承するクラスは、__init__
メソッドの実行が終了した後、インスタンスと同様に不変です。他の人が指摘しているように、純粋なpythonであるため、ベースobject
およびtype
からの特別なメソッドの変更を誰かが使用するのを止めることはできませんが、クラス/インスタンスを事故。
クラス作成プロセスをメタクラスでハイジャックすることで機能します。
"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.
"""
# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
for component in '''attr item slice'''.split():
mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])
def checked_call(_self, name, method, *args, **kwargs):
"""Calls special method method(*args, **kw) on self if mutable."""
self = args[0] if isinstance(_self, object) else _self
if not getattr(self, '__mutable__', True):
# self told us it's immutable, so raise an error
cname= (self if isinstance(self, type) else self.__class__).__name__
raise TypeError('%s is immutable, %s disallowed' % (cname, name))
return method(*args, **kwargs)
def method_wrapper(_self, name):
"Wrap a special method to check for mutability."
method = getattr(_self, name)
def wrapper(*args, **kwargs):
return checked_call(_self, name, method, *args, **kwargs)
wrapper.__= name
wrapper.__doc__ = method.__doc__
return wrapper
def wrap_mutating_methods(_self):
"Place the wrapper methods on mutative special methods of _self"
for name in mutation_methods:
if hasattr(_self, name):
method = method_wrapper(_self, name)
type.__setattr__(_self, name, method)
def set_mutability(self, ismutable):
"Set __mutable__ by using the unprotected __setattr__"
b = _MetaImmutable if isinstance(self, type) else Immutable
super(b, self).__setattr__('__mutable__', ismutable)
class _MetaImmutable(type):
'''The metaclass of Immutable. Wraps __init__ methods via __call__.'''
def __init__(cls, *args, **kwargs):
# Make class mutable for wrapping special methods
set_mutability(cls, True)
wrap_mutating_methods(cls)
# Disable mutability
set_mutability(cls, False)
def __call__(cls, *args, **kwargs):
'''Make an immutable instance of cls'''
self = cls.__new__(cls)
# Make the instance mutable for initialization
set_mutability(self, True)
# Execute cls's custom initialization on this instance
self.__init__(*args, **kwargs)
# Disable mutability
set_mutability(self, False)
return self
# Given a class T(metaclass=_MetaImmutable), mutative special methods which
# already exist on _MetaImmutable (a basic type) cannot be over-ridden
# programmatically during _MetaImmutable's instantiation of T, because the
# first place python looks for a method on an object is on the object's
# __class__, and T.__class__ is _MetaImmutable. The two extant special
# methods on a basic type are __setattr__ and __delattr__, so those have to
# be explicitly overridden here.
def __setattr__(cls, name, value):
checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)
def __delattr__(cls, name, value):
checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)
class Immutable(object):
"""Inherit from this class to make an immutable object.
__init__ methods of subclasses are executed by _MetaImmutable.__call__,
which enables mutability for the duration.
"""
__metaclass__ = _MetaImmutable
class T(int, Immutable): # Checks it works with multiple inheritance, too.
"Class for testing immutability semantics"
def __init__(self, b):
self.b = b
@classmethod
def class_mutation(cls):
cls.a = 5
def instance_mutation(self):
self.c = 1
def __iadd__(self, o):
pass
def not_so_special_mutation(self):
self +=1
def immutabilityTest(f, name):
"Call f, which should try to mutate class T or T instance."
try:
f()
except TypeError, e:
assert 'T is immutable, %s disallowed' % name in e.args
else:
raise RuntimeError('Immutability failed!')
immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')
setattrをオーバーライドし、引き続きinitを使用して変数を設定できます。スーパークラスsetattrを使用します。ここにコードがあります。
class Immutable: __slots__ =( 'a'、 'b') def __init __(self、a、b): super().__ setattr __( 'a'、a) super().__ setattr __( 'b'、b) def __str __(self): return "" .format( self.a、self.b) def __setattr __(self、* ignored): raise NotImplementedError def __delattr __(self、*無視): NotImplementedError を発生させます
サードパーティの attr
モジュールは この機能 を提供します。
編集:python 3.7は、このアイデアを @dataclass
でstdlibに採用しました。
$ pip install attrs
$ python
>>> @attr.s(frozen=True)
... class C(object):
... x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
...
attr.exceptions.FrozenInstanceError: can't set attribute
attr
は、__setattr__
をオーバーライドすることで凍結されたクラスを実装し、ドキュメントによると、インスタンス化のたびにパフォーマンスにわずかな影響があります。
クラスをデータ型として使用する習慣がある場合は、attr
が特に便利な場合があります(ただし、魔法はしません)。特に、repr、init、hash、およびすべての比較関数を含む、9つのdunder(__X__)メソッドを(それらのいずれかをオフにしない限り)作成します。
attr
は __slots__
のヘルパー も提供します。
私はアレックスと同じアイデアを使用しました:メタクラスと「初期化マーカー」ですが、__ setattr__の上書きと組み合わせて:
>>> from abc import ABCMeta
>>> _INIT_MARKER = '_@_in_init_@_'
>>> class _ImmutableMeta(ABCMeta):
...
... """Meta class to construct Immutable."""
...
... def __call__(cls, *args, **kwds):
... obj = cls.__new__(cls, *args, **kwds)
... object.__setattr__(obj, _INIT_MARKER, True)
... cls.__init__(obj, *args, **kwds)
... object.__delattr__(obj, _INIT_MARKER)
... return obj
...
>>> def _setattr(self, name, value):
... if hasattr(self, _INIT_MARKER):
... object.__setattr__(self, name, value)
... else:
... raise AttributeError("Instance of '%s' is immutable."
... % self.__class__.__name__)
...
>>> def _delattr(self, name):
... raise AttributeError("Instance of '%s' is immutable."
... % self.__class__.__name__)
...
>>> _im_dict = {
... '__doc__': "Mix-in class for immutable objects.",
... '__copy__': lambda self: self, # self is immutable, so just return it
... '__setattr__': _setattr,
... '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)
注:メタクラスを直接呼び出して、Python 2.xと3.xの両方で機能するようにします。
>>> class T1(Immutable):
...
... def __init__(self, x=1, y=2):
... self.x = x
... self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.
スロットでも動作します...:
>>> class T2(Immutable):
...
... __slots__ = 's1', 's2'
...
... def __init__(self, s1, s2):
... self.s1 = s1
... self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.
...および多重継承:
>>> class T3(T1, T2):
...
... def __init__(self, x, y, s1, s2):
... T1.__init__(self, x, y)
... T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.
ただし、可変属性は可変のままであることに注意してください。
>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]
ここに実際に含まれていないものの1つは、完全な不変性です。親オブジェクトだけでなく、すべての子も同様です。 tuples/frozensetsは、例えば不変かもしれませんが、それが属するオブジェクトはそうではないかもしれません。以下は、不変性を完全に強制するというまともな仕事をする小さな(不完全な)バージョンです。
# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
l = [a,b]
# We can reassign in a list
l[0] = c
# But not a Tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2
li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception
# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.
class ImmutableObject(object):
def __init__(self, inobj):
self._inited = False
self._inobj = inobj
self._inited = True
def __repr__(self):
return self._inobj.__repr__()
def __str__(self):
return self._inobj.__str__()
def __getitem__(self, key):
return ImmutableObject(self._inobj.__getitem__(key))
def __iter__(self):
return self._inobj.__iter__()
def __setitem__(self, key, value):
raise AttributeError, 'Object is read-only'
def __getattr__(self, key):
x = getattr(self._inobj, key)
if callable(x):
return x
else:
return ImmutableObject(x)
def __hash__(self):
return self._inobj.__hash__()
def __eq__(self, second):
return self._inobj.__eq__(second)
def __setattr__(self, attr, value):
if attr not in ['_inobj', '_inited'] and self._inited == True:
raise AttributeError, 'Object is read-only'
object.__setattr__(self, attr, value)
Initの最終ステートメントでsetAttrをオーバーライドできます。構築することはできますが、変更はできません。明らかに、usintオブジェクトでオーバーライドできます。setAttrですが、実際にはほとんどの言語には何らかの形のリフレクションがあるため、不変性は常に漏れやすい抽象化です。不変性とは、クライアントが誤ってオブジェクトの契約に違反するのを防ぐことです。私が使う:
=============================
提供された元のソリューションは間違っていました。これは here のソリューションを使用してコメントに基づいて更新されました
元のソリューションは興味深い方法で間違っているため、下部に含まれています。
===============================
class ImmutablePair(object):
__initialised = False # a class level variable that should always stay false.
def __init__(self, a, b):
try :
self.a = a
self.b = b
finally:
self.__initialised = True #an instance level variable
def __setattr__(self, key, value):
if self.__initialised:
self._raise_error()
else :
super(ImmutablePair, self).__setattr__(key, value)
def _raise_error(self, *args, **kw):
raise NotImplementedError("Attempted To Modify Immutable Object")
if __== "__main__":
immutable_object = ImmutablePair(1,2)
print immutable_object.a
print immutable_object.b
try :
immutable_object.a = 3
except Exception as e:
print e
print immutable_object.a
print immutable_object.b
出力:
1
2
Attempted To Modify Immutable Object
1
2
=====================================
元の実装:
クラスsetattrメソッドをオーバーライドしているときに複数のオブジェクトの作成を防ぐため、これは実際には機能しないことがコメントで正しく指摘されました。つまり、2番目はself.a = willとして作成できません2回目の初期化に失敗します。
class ImmutablePair(object):
def __init__(self, a, b):
self.a = a
self.b = b
ImmutablePair.__setattr__ = self._raise_error
def _raise_error(self, *args, **kw):
raise NotImplementedError("Attempted To Modify Immutable Object")
別のアプローチは、インスタンスを不変にするラッパーを作成することです。
class Immutable(object):
def __init__(self, wrapped):
super(Immutable, self).__init__()
object.__setattr__(self, '_wrapped', wrapped)
def __getattribute__(self, item):
return object.__getattribute__(self, '_wrapped').__getattribute__(item)
def __setattr__(self, key, value):
raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))
__delattr__ = __setattr__
def __iter__(self):
return object.__getattribute__(self, '_wrapped').__iter__()
def next(self):
return object.__getattribute__(self, '_wrapped').next()
def __getitem__(self, item):
return object.__getattribute__(self, '_wrapped').__getitem__(item)
immutable_instance = Immutable(my_instance)
これは、一部のインスタンスのみが不変でなければならない状況で役立ちます(関数呼び出しのデフォルト引数など)。
次のような不変の工場でも使用できます。
@classmethod
def immutable_factory(cls, *args, **kwargs):
return Immutable(cls.__init__(*args, **kwargs))
object.__setattr__
からも保護しますが、Pythonの動的な性質により、他のトリックに陥ります。
以下の基本ソリューションは、次のシナリオに対応しています。
__init__()
は、通常どおり属性にアクセスして書き込むことができます。考え方は、__setattr__
メソッドをオーバーライドし、オブジェクトの凍結ステータスが変更されるたびにその実装を置き換えることです。
そのため、これら2つの実装を保存し、要求時にそれらを切り替えるメソッド(_freeze
)が必要です。
このメカニズムは、以下に示すように、ユーザークラス内に実装するか、特別なFreezer
クラスから継承できます。
class Freezer:
def _freeze(self, do_freeze=True):
def raise_sa(*args):
raise AttributeError("Attributes are frozen and can not be changed!")
super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])
def __setattr__(self, key, value):
return self._active_setattr(key, value)
class A(Freezer):
def __init__(self):
self._freeze(False)
self.x = 10
self._freeze()