web-dev-qa-db-ja.com

Pythonで不変オブジェクトを作成する方法は?

私はこれを必要としたことは一度もありませんが、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]などによってデータにアクセスする追加の可能性を除いて、うまく機能します。 、geititemsetattributeなどを実装しないことで、非常に簡単になると思います。しかし、自分でやるのではなく、怠け者なので賞金を提供します。 :)

161
Lennart Regebro

私が考えていたさらに別のソリューション:元のコードと同じ動作を得る最も簡単な方法は

Immutable = collections.namedtuple("Immutable", ["a", "b"])

[0]などを介して属性にアクセスできるという問題は解決しませんが、少なくともかなり短く、pickleおよびcopyと互換性があるという追加の利点があります。

namedtupleこの回答 で説明したものと同様のタイプを作成します。つまり、Tupleから派生し、__slots__を使用します。 Python 2.6以降で利用可能です。

103
Sven Marnach

これを行う最も簡単な方法は、__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__()を回避する可能性を考慮しない理由です。誰かがこれを行うと、それは彼女自身のリスクになります。

72
Sven Marnach

..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")
49
jfs

別のアイデアは、__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__()を使用します。

38
Sven Marnach

@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
18
PaoloVictor

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)
>>>      
10
Duncan

エレガントソリューションです:

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

このクラスから継承し、コンストラクターでフィールドを初期化すると、すべて設定されます。

5

他の優れた答えに加えて、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__の使用は推奨されません。属性の作成を防ぐために他の方法を使用することをお勧めします。

4
TheEspinosa

振る舞いを持つオブジェクトに興味がある場合、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でもテスト済み)。
唯一の欠点は、継承ツリーが少し奇妙だということです。しかし、これは通常あなたが遊ぶものではありません。

3
rob

__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__でオブジェクトを変更できるため、これではまだ十分ではありませんが、アイデアは得られます。

3
Ned Batchelder

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
2
user10018213

この方法で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__など)をオーバーライドする必要がある場合があります。

2
dangirsh

少し前にこれが必要だったので、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

これが役に立てば、説明したように名前付きタプルをラップしますが、インスタンス化はもっと簡単になります。

2
theengineear

次の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__')
2
Alex Coventry

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 
を発生させます
1
Shameer Ali

サードパーティの 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__のヘルパー も提供します。

1
cmc

私はアレックスと同じアイデアを使用しました:メタクラスと「初期化マーカー」ですが、__ 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]
0
Michael Amrhein

ここに実際に含まれていないものの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)
0
Corley Brigman

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")
0
phil_20686

別のアプローチは、インスタンスを不変にするラッパーを作成することです。

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の動的な性質により、他のトリックに陥ります。

0
Mark Horvath

以下の基本ソリューションは、次のシナリオに対応しています。

  • __init__()は、通常どおり属性にアクセスして書き込むことができます。
  • OBJECTがattributes変更のみで凍結された後:

考え方は、__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()
0
ipap