Làm thế nào để tạo một đối tượng bất biến trong Python?


181

Mặc dù tôi chưa bao giờ cần điều này, nhưng tôi nhận ra rằng việc tạo ra một vật thể bất biến trong Python có thể hơi khó. Bạn không thể ghi đè __setattr__, vì sau đó bạn thậm chí không thể đặt thuộc tính trong __init__. Phân lớp một tuple là một mẹo hoạt động:

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

Nhưng sau đó bạn có quyền truy cập vào abcác biến thông qua self[0], và self[1]điều đó gây khó chịu.

Điều này có thể có trong Pure Python không? Nếu không, tôi sẽ làm thế nào với phần mở rộng C?

(Câu trả lời chỉ hoạt động trong Python 3 là chấp nhận được).

Cập nhật:

Vì vậy, tuple phân lớp là cách thực hiện trong Pure Python, hoạt động tốt ngoại trừ khả năng truy cập dữ liệu bổ sung [0], [1]v.v. Tôi nghi ngờ sẽ khá đơn giản, chỉ bằng cách không thực hiện bất kỳ geititemhoặc setattribute, v.v. Nhưng thay vì tự mình làm, tôi đưa ra một khoản tiền thưởng cho điều đó, bởi vì tôi lười biếng. :)


2
Không phải mã của bạn tạo điều kiện truy cập vào các thuộc tính thông qua .a.b? Rốt cuộc đó là những gì thuộc tính tồn tại.
Sven Marnach

1
@Sven Marnach: Có, nhưng [0] và [1] vẫn hoạt động, và tại sao họ lại như vậy? Tôi không muốn họ. :) Có lẽ ý tưởng về một đối tượng bất biến với các thuộc tính là vô nghĩa? :-)
Lennart Regebro

2
Chỉ cần một lưu ý khác: NotImplementedchỉ có nghĩa là một giá trị trả về cho so sánh phong phú. Giá trị trả về cho __setatt__()dù sao cũng khá vô nghĩa, vì bạn thường sẽ không thấy nó. Mã như immutable.x = 42sẽ âm thầm không làm gì. Bạn nên nâng một TypeErrorthay thế.
Sven Marnach

1
@Sven Marnach: OK, tôi đã rất ngạc nhiên, vì tôi nghĩ bạn có thể nâng cao NotIm Hiện thực trong tình huống này, nhưng điều đó mang lại một lỗi kỳ lạ. Vì vậy, tôi đã trả lại nó, và nó dường như làm việc. TypeError có ý nghĩa rõ ràng khi tôi thấy bạn sử dụng nó.
Lennart Regebro

1
@Lennart: Bạn có thể tăng NotImplementedError, nhưng đó TypeErrorlà những gì một tuple tăng nếu bạn cố gắng sửa đổi nó.
Sven Marnach

Câu trả lời:


115

Một giải pháp khác tôi vừa nghĩ ra: Cách đơn giản nhất để có được hành vi giống như mã gốc của bạn là

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

Nó không giải quyết vấn đề mà các thuộc tính có thể được truy cập thông qua [0], v.v., nhưng ít nhất nó ngắn hơn đáng kể và cung cấp lợi thế bổ sung là tương thích với picklecopy.

namedtupletạo ra một loại tương tự như những gì tôi mô tả trong câu trả lời này , tức là bắt nguồn từ tuplevà sử dụng __slots__. Nó có sẵn trong Python 2.6 trở lên.


7
Ưu điểm của biến thể này so với tương tự viết tay (ngay cả trên Python 2.5 (sử dụng verbosetham số cho namedtuplemã dễ dàng được tạo ra)) là giao diện / triển khai duy nhất của a namedtuplecó thể thích hợp hơn hàng chục giao diện / cách viết tay hơi khác làm gần như điều tương tự
jfs

2
OK, bạn nhận được "câu trả lời tốt nhất", bởi vì đó là cách dễ nhất để làm điều đó. Sebastian nhận được tiền thưởng cho việc thực hiện Cython ngắn. Chúc mừng!
Lennart Regebro

1
Một đặc điểm khác của các đối tượng bất biến là khi bạn truyền chúng dưới dạng tham số thông qua hàm, chúng được sao chép theo giá trị, thay vì tham chiếu khác được tạo. Would namedtuples được sao chép theo giá trị khi đi qua các chức năng?
hlin117

4
@ hlin117: Mọi tham số được truyền dưới dạng tham chiếu đến một đối tượng trong Python, bất kể nó có thể thay đổi hay bất biến. Đối với các đối tượng không thay đổi, việc tạo một bản sao sẽ vô cùng đặc biệt - vì dù sao bạn không thể thay đổi đối tượng, bạn cũng có thể chuyển tham chiếu đến đối tượng ban đầu.
Sven Marnach

Bạn có thể sử dụng têntuple bên trong lớp thay vì khởi tạo đối tượng bên ngoài không? Tôi rất mới với python nhưng lợi thế cho câu trả lời khác của bạn là tôi có thể có một lớp ẩn các chi tiết và cũng có sức mạnh của những thứ như các tham số tùy chọn. Nếu tôi chỉ nhìn vào câu trả lời này, có vẻ như tôi cần phải có mọi thứ sử dụng bộ khởi tạo lớp của mình có tên là tuples. Cảm ơn bạn cho cả hai câu trả lời.
Asaf

78

Cách dễ nhất để làm điều này là sử dụng __slots__:

class A(object):
    __slots__ = []

Trường hợp Alà không thay đổi bây giờ, kể từ khi bạn không thể thiết lập bất kỳ thuộc tính trên chúng.

Nếu bạn muốn các thể hiện của lớp chứa dữ liệu, bạn có thể kết hợp điều này với việc xuất phát từ 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

Chỉnh sửa : Nếu bạn muốn thoát khỏi việc lập chỉ mục, bạn có thể ghi đè __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

Lưu ý rằng bạn không thể sử dụng operator.itemgettercho các thuộc tính trong trường hợp này, vì điều này sẽ dựa vào Point.__getitem__()thay vì tuple.__getitem__(). Fuerthermore điều này sẽ không ngăn chặn việc sử dụng tuple.__getitem__(p, 0), nhưng tôi khó có thể tưởng tượng điều này sẽ tạo thành một vấn đề như thế nào.

Tôi không nghĩ rằng cách "đúng" để tạo ra một đối tượng bất biến là viết phần mở rộng C. Python thường dựa vào những người triển khai thư viện và người dùng thư viện là người lớn đồng ý và thay vì thực sự thực thi một giao diện, giao diện nên được nêu rõ trong tài liệu. Đây là lý do tại sao tôi không xem xét khả năng phá vỡ một ghi đè __setattr__()bằng cách gọi object.__setattr__()một vấn đề. Nếu ai đó làm điều này, đó là rủi ro của riêng cô ấy.


1
Nó không phải là một ý tưởng tốt hơn để sử dụng tupleở đây __slots__ = (), chứ không phải là __slots__ = []? (Chỉ cần làm rõ)
user225312

1
@sukhbir: Tôi nghĩ điều này không quan trọng. Tại sao bạn thích một tuple?
Sven Marnach

1
@Sven: Tôi đồng ý rằng nó sẽ không thành vấn đề (ngoại trừ phần tốc độ mà chúng ta có thể bỏ qua), nhưng tôi nghĩ về nó theo cách này: __slots__sẽ không được thay đổi phải không? Mục đích của nó là xác định một lần những thuộc tính nào có thể được đặt. Vì vậy, không phải là tuplemột sự lựa chọn tự nhiên trong trường hợp như vậy?
dùng225312

5
Nhưng với một sản phẩm nào __slots__tôi không thể đặt bất kỳ thuộc tính nào . Và nếu tôi có __slots__ = ('a', 'b')thì các thuộc tính a và b vẫn có thể thay đổi.
Lennart Regebro

Nhưng giải pháp của bạn tốt hơn là ghi đè __setattr__vì vậy đó là một cải tiến so với của tôi. +1 :)
Lennart Regebro

50

.. làm thế nào để làm điều đó "đúng" trong C ..

Bạn có thể sử dụng Cython để tạo loại tiện ích mở rộng cho 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

Nó hoạt động cả Python 2.x và 3.

Xét nghiệm

# 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'

Nếu bạn không phiền khi lập chỉ mục hỗ trợ thì @Sven Marnachcollections.namedtuple đề xuất là có thể:

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

@Lennart: Trường hợp của namedtuple(hoặc chính xác hơn là loại được trả về bởi hàm namedtuple()) là bất biến. Chắc chắn rồi.
Sven Marnach

@Lennart Regebro: namedtuplevượt qua tất cả các bài kiểm tra (ngoại trừ hỗ trợ lập chỉ mục). Tôi đã bỏ lỡ yêu cầu gì?
jfs

Vâng, bạn đã đúng, tôi đã tạo một loại có tên, khởi tạo nó và sau đó thực hiện thử nghiệm trên loại thay vì ví dụ. Heh. :-)
Lennart Regebro

Tôi có thể hỏi tại sao một người cần tham khảo yếu ở đây?
McSinyx

1
@McSinyx: nếu không, các đối tượng không thể được sử dụng trong các bộ sưu tập của fraref. Chính xác thì có gì __weakref__trong Python?
jfs

40

Một ý tưởng khác là không cho phép __setattr__và sử dụng hoàn toàn object.__setattr__trong hàm tạo:

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

Tất nhiên bạn có thể sử dụng object.__setattr__(p, "x", 3)để sửa đổi một Pointthể hiện p, nhưng việc triển khai ban đầu của bạn gặp phải vấn đề tương tự (thử tuple.__setattr__(i, "x", 42)trên mộtImmutable thể hiện).

Bạn có thể áp dụng thủ thuật tương tự trong triển khai ban đầu của mình: loại bỏ __getitem__()và sử dụng tuple.__getitem__()các chức năng thuộc tính của bạn.


11
Tôi sẽ không quan tâm đến việc ai đó cố tình sửa đổi đối tượng bằng cách sử dụng siêu lớp ' __setattr__, bởi vì vấn đề là không thể đánh lừa được. Vấn đề là làm rõ rằng nó không nên được sửa đổi và để tránh sửa đổi do nhầm lẫn.
zvone

18

Bạn có thể tạo một trình @immutabletrang trí ghi đè lên __setattr__ thay đổi __slots__danh sách trống, sau đó trang trí __init__phương thức với nó.

Chỉnh sửa: Như OP đã lưu ý, việc thay đổi __slots__thuộc tính chỉ ngăn việc tạo các thuộc tính mới , không phải sửa đổi.

Edit2: Đây là một triển khai:

Edit3: Sử dụng __slots__phá vỡ mã này, bởi vì nếu dừng việc tạo đối tượng __dict__. Tôi đang tìm kiếm một sự thay thế.

Edit4: Vâng, đó là nó. Đó là một hackish, nhưng hoạt động như một bài tập :-)

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

1
Làm cho một trang trí (lớp?) Hoặc siêu dữ liệu ra khỏi giải pháp thực sự là một ý tưởng tốt, nhưng câu hỏi là giải pháp là gì. :)
Lennart Regebro

3
object.__setattr__()phá vỡ stackoverflow.com/questions/4828080/
Mạnh

Thật. Tôi chỉ thực hiện như một bài tập về trang trí.
PaoloVictor

13

Sử dụng một Dataclass Frozen

Đối với Python 3.7+, bạn có thể sử dụng Lớp dữ liệu với frozen=Truetùy chọn , đây là cách rất dễ duy trì để làm những gì bạn muốn.

Nó sẽ trông giống như thế:

from dataclasses import dataclass

@dataclass(frozen=True)
class Immutable:
    a: Any
    b: Any

gợi ý kiểu là bắt buộc cho các trường của dataclass, tôi đã sử dụng Bất kỳ từ typingmô-đun .

Lý do KHÔNG nên sử dụng Namedtuple

Trước Python 3.7, người ta thường thấy các tên được sử dụng như là các đối tượng bất biến. Nó có thể khó khăn theo nhiều cách, một trong số đó là __eq__phương thức giữa các tên được đặt tên không xem xét các lớp của các đối tượng. Ví dụ:

from collections import namedtuple

ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])

obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)

obj1 == obj2  # will be True

Như bạn thấy, ngay cả khi các loại obj1obj2khác nhau, ngay cả khi tên trường của chúng khác nhau, obj1 == obj2vẫn đưa ra True. Đó là bởi vì __eq__phương thức được sử dụng là phương pháp của tuple, chỉ so sánh các giá trị của các trường được đưa ra vị trí của chúng. Đó có thể là một nguồn lỗi rất lớn, đặc biệt nếu bạn đang phân lớp các lớp này.


10

Tôi không nghĩ rằng điều đó là hoàn toàn có thể, ngoại trừ bằng cách sử dụng một tuple hoặc một tên được đặt tên. Không có vấn đề gì, nếu bạn ghi đè __setattr__()người dùng luôn có thể bỏ qua nó bằng cách gọi object.__setattr__()trực tiếp. Bất kỳ giải pháp phụ thuộc vào __setattr__được đảm bảo không hoạt động.

Sau đây là về gần nhất bạn có thể nhận được mà không cần sử dụng một số loại tuple:

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__

nhưng nó sẽ vỡ nếu bạn cố gắng hết sức:

>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2

nhưng Sven sử dụng namedtuple thực sự là bất biến.

Cập nhật

Vì câu hỏi đã được cập nhật để hỏi cách thực hiện đúng trong C, đây là câu trả lời của tôi về cách thực hiện đúng trong Cython:

Đầu tiên 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)

và a setup.pyđể biên dịch nó (sử dụng lệnh 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
)

Sau đó, để thử nó:

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

Cảm ơn mã Cython, Cython thật tuyệt vời. Việc thực hiện JF Sebastians với chỉ số là gọn gàng hơn và đến trước, vì vậy anh ta nhận được tiền thưởng.
Lennart Regebro

5

Tôi đã tạo các lớp bất biến bằng cách ghi đè __setattr__và cho phép thiết lập nếu người gọi là __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)

Điều này vẫn chưa đủ, vì nó cho phép mọi người ___init__thay đổi đối tượng, nhưng bạn hiểu ý.


object.__setattr__()phá vỡ stackoverflow.com/questions/4828080/
Mạnh

3
Sử dụng kiểm tra ngăn xếp để đảm bảo người gọi __init__không hài lòng lắm.
gb.

5

Ngoài các câu trả lời tuyệt vời khác, tôi muốn thêm một phương thức cho python 3.4 (hoặc có thể là 3,3). Câu trả lời này được xây dựng dựa trên một số câu trả lời trước câu hỏi này.

Trong python 3.4, bạn có thể sử dụng các thuộc tính không có setters để tạo các thành viên lớp không thể sửa đổi. (Trong các phiên bản trước, việc gán cho các thuộc tính mà không có trình cài đặt là có thể.)

class A:
    __slots__=['_A__a']
    def __init__(self, aValue):
      self.__a=aValue
    @property
    def a(self):
        return self.__a

Bạn có thể sử dụng nó như thế này:

instance=A("constant")
print (instance.a)

cái nào sẽ in "constant"

Nhưng gọi instance.a=10sẽ gây ra:

AttributeError: can't set attribute

Giải thích: các thuộc tính không có setters là một tính năng rất gần đây của python 3.4 (và tôi nghĩ 3.3). Nếu bạn cố gắng gán cho một thuộc tính như vậy, một lỗi sẽ được đưa ra. Sử dụng các vị trí tôi hạn chế các membervariabled __A_a(đó là __a).

Vấn đề: Việc gán cho _A__avẫn có thể ( instance._A__a=2). Nhưng nếu bạn gán cho một biến riêng, đó là lỗi của bạn ...

Câu trả lời này trong số những người khác, tuy nhiên, không khuyến khích việc sử dụng __slots__. Sử dụng các cách khác để ngăn chặn việc tạo thuộc tính có thể được ưa thích hơn.


propertycũng có sẵn trên Python 2 (nhìn vào mã trong chính câu hỏi). Nó không tạo ra một đối tượng bất biến, hãy thử các bài kiểm tra từ câu trả lời của tôi , ví dụ, instance.b = 1tạo một bthuộc tính mới .
jfs

Đúng, câu hỏi thực sự là làm thế nào để ngăn chặn việc làm A().b = "foo"tức là không cho phép thiết lập các thuộc tính mới.
Lennart Regebro

Chính xác mà không có setter sẽ gây ra lỗi trong python 3.4 nếu bạn cố gán cho thuộc tính đó. Trong các phiên bản trước, setter được tạo ngầm.
Bernhard

@Lennart: Giải pháp của tôi là một câu trả lời cho một tập hợp các trường hợp sử dụng cho các đối tượng không thay đổi và bổ sung cho các câu trả lời trước đó. Một lý do tôi có thể muốn một đối tượng bất biến là để tôi có thể làm cho nó có thể băm được, trong trường hợp đó giải pháp của tôi có thể hoạt động. Nhưng bạn đã đúng, đây không phải là một đối tượng bất biến.
Bernhard

@ jf-sebastian: Đã thay đổi câu trả lời của tôi để sử dụng các vị trí để ngăn chặn việc tạo thuộc tính. Điều mới trong câu trả lời của tôi so với các câu trả lời khác, là tôi sử dụng các thuộc tính của python3.4 để tránh thay đổi các thuộc tính tồn tại. Mặc dù đạt được điều tương tự trong các câu trả lời phổ biến, mã của tôi ngắn hơn vì sự thay đổi trong hành vi của các thuộc tính.
Bernhard

5

Đây là một giải pháp tao nhã :

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

Kế thừa từ lớp này, khởi tạo các trường của bạn trong hàm tạo và bạn đã thiết lập xong.


1
nhưng với logic này nó có thể gán thuộc tính mới cho các đối tượng
Javed

3

Nếu bạn quan tâm đến các đối tượng có hành vi, thì nametuple gần như là giải pháp của bạn.

Như được mô tả ở dưới cùng của tài liệu có tên , bạn có thể rút ra lớp của riêng bạn từ têntuple; và sau đó, bạn có thể thêm hành vi bạn muốn.

Ví dụ: (mã được lấy trực tiếp từ tài liệu ):

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)

Điều này sẽ dẫn đến:

Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

Cách tiếp cận này hoạt động cho cả Python 3 và Python 2.7 (cũng đã được thử nghiệm trên IronPython).
Nhược điểm duy nhất là cây thừa kế hơi kỳ lạ; nhưng đây không phải là thứ bạn thường chơi


1
Python 3.6+ hỗ trợ điều này trực tiếp, bằng cách sử dụngclass Point(typing.NamedTuple):
Elazar

3

Các lớp kế thừa từ Immutablelớp sau là bất biến, như các thể hiện của chúng, sau khi __init__phương thức của chúng kết thúc thực thi. Vì đó là con trăn thuần chủng, như những người khác đã chỉ ra, không có gì ngăn cản ai đó sử dụng các phương pháp đặc biệt đột biến từ cơ sở objecttype , nhưng điều này đủ để ngăn chặn bất kỳ ai đột biến một lớp / trường hợp một cách tình cờ.

Nó hoạt động bằng cách chiếm quyền điều khiển quá trình tạo lớp bằng siêu dữ liệu.

"""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__ = 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

Tôi cần điều này một chút trước đây và quyết định tạo một gói Python cho nó. Phiên bản ban đầu là trên PyPI bây giờ:

$ pip install immutable

Để sử dụng:

>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1

Tài liệu đầy đủ tại đây: https://github.com/theengineear/immutable

Hy vọng nó sẽ giúp, nó kết thúc một cái tên như đã được thảo luận, nhưng làm cho việc khởi tạo đơn giản hơn nhiều.


2

Cách này không ngừng hoạt object.__setattr__động, nhưng tôi vẫn thấy nó hữu ích:

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)

bạn có thể cần ghi đè thêm nhiều thứ (như __setitem__) tùy theo trường hợp sử dụng.


Tôi đã nghĩ ra một cái gì đó tương tự trước khi tôi thấy cái này, nhưng được sử dụng getattrđể tôi có thể cung cấp một giá trị mặc định cho frozen. Điều đó đơn giản hóa một chút. stackoverflow.com/a/22545808/5987
Đánh dấu tiền chuộc

Tôi thích cách tiếp cận này tốt nhất, nhưng bạn không cần __new__ghi đè. Bên trong __setattr__chỉ cần thay thế điều kiện bằngif name != '_frozen' and getattr(self, "_frozen", False)
Pete Cacioppi

Ngoài ra, không cần phải đóng băng lớp khi xây dựng. Bạn có thể đóng băng nó bất cứ lúc nào nếu bạn cung cấp một freeze()chức năng. Đối tượng sau đó sẽ "đóng băng một lần". Cuối cùng, lo lắng object.__setattr__là ngớ ngẩn, bởi vì "chúng ta đều là người lớn ở đây".
Pete Cacioppi

2

Kể từ Python 3.7, bạn có thể sử dụng trình @dataclasstrang trí trong lớp của mình và nó sẽ bất biến như một cấu trúc! Mặc dù, nó có thể hoặc không thể thêm một __hash__()phương thức vào lớp của bạn. Trích dẫn:

hàm băm () được sử dụng bởi hàm băm () tích hợp và khi các đối tượng được thêm vào các bộ sưu tập băm như từ điển và bộ. Có một hàm băm () ngụ ý rằng các thể hiện của lớp là bất biến. Tính tương tác là một thuộc tính phức tạp phụ thuộc vào ý định của lập trình viên, sự tồn tại và hành vi của eq () và các giá trị của eq và cờ đóng băng trong trình trang trí dataclass ().

Theo mặc định, dataclass () sẽ không thêm phương thức hash () trừ khi nó an toàn để làm như vậy. Nó cũng sẽ không thêm hoặc thay đổi một phương thức hash () được xác định rõ ràng hiện có . Đặt thuộc tính lớp hash = Không có ý nghĩa cụ thể đối với Python, như được mô tả trong tài liệu băm ().

Nếu hàm băm () không được xác định rõ ràng hoặc nếu nó được đặt thành Không, thì dataclass () có thể thêm phương thức hàm băm () ẩn . Mặc dù không được khuyến nghị, bạn có thể buộc dataclass () tạo phương thức hash () với unsafe_hash = True. Đây có thể là trường hợp nếu lớp của bạn là bất biến về mặt logic nhưng dù sao cũng có thể bị đột biến. Đây là một trường hợp sử dụng chuyên ngành và cần được xem xét cẩn thận.

Dưới đây là ví dụ từ các tài liệu được liên kết ở trên:

@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

1
bạn cần sử dụng frozen, nghĩa là @dataclass(frozen=True), nhưng về cơ bản, nó chặn sử dụng __setattr____delattr__thích trong hầu hết các câu trả lời khác ở đây. Nó chỉ thực hiện theo cách tương thích với các tùy chọn khác của dataclass.
CS

2

Bạn có thể ghi đè setattr và vẫn sử dụng init để đặt biến. Bạn sẽ sử dụng setattr siêu hạng . đây là mã

lớp bất biến:
    __slots__ = ('a', 'b')
    def __init __ (tự, a, b):
        siêu () .__ setattr __ ('a', a)
        siêu () .__ setattr __ ('b', b)

    def __str __ (tự):
        trả về "" .format (self.a, self.b)

    def __setattr __ (tự, * bỏ qua):
        nâng cao NotIm HiệnedError

    def __delattr __ (tự, * bỏ qua):
        nâng cao NotIm HiệnedError

Hoặc chỉ passthay vìraise NotImplementedError
jonathan.scholbach

Hoàn toàn không phải là một ý tưởng hay để thực hiện "vượt qua" trong __setattr__ và __delattr__ trong trường hợp này. Lý do đơn giản là nếu ai đó gán giá trị cho một trường / thuộc tính, thì họ tự nhiên mong đợi rằng trường sẽ được thay đổi. Nếu bạn muốn đi theo con đường "ít bất ngờ nhất" (như bạn nên), thì bạn phải đưa ra một lỗi. Nhưng tôi không chắc liệu NotIm HiệnedError có phải là người phù hợp để nâng cao hay không. Tôi muốn nêu lên một cái gì đó như "Trường / tài sản là bất biến." lỗi ... Tôi nghĩ rằng một ngoại lệ tùy chỉnh nên được ném.
darlove

1

attrMô-đun bên thứ ba cung cấp chức năng này .

Chỉnh sửa: python 3.7 đã áp dụng ý tưởng này vào stdlib với @dataclass.

$ 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

attrthực hiện các lớp đông lạnh bằng cách ghi đè __setattr__và có tác động hiệu suất nhỏ tại mỗi thời điểm khởi tạo, theo tài liệu.

Nếu bạn đang có thói quen sử dụng các lớp làm kiểu dữ liệu, attrcó thể đặc biệt hữu ích vì nó sẽ chăm sóc nồi hơi cho bạn (nhưng không thực hiện bất kỳ phép thuật nào). Cụ thể, nó viết chín phương thức dunder (__X__) cho bạn (trừ khi bạn tắt bất kỳ phương thức nào trong số chúng), bao gồm repr, init, hash và tất cả các hàm so sánh.

attrcũng cung cấp một người trợ giúp cho__slots__ .


1

Vì vậy, tôi đang viết tương ứng với python 3:

I) với sự trợ giúp của trình trang trí lớp dữ liệu và đặt Frozen = True. chúng ta có thể tạo ra các đối tượng bất biến trong python.

cho việc này cần nhập lớp dữ liệu từ các lớp dữ liệu lib và cần đặt Frozen = True

Ví dụ.

từ dataclass nhập dữ liệu

@dataclass(frozen=True)
class Location:
    name: str
    longitude: float = 0.0
    latitude: float = 0.0

o / p:

l = Địa điểm ("Delhi", 112.345, 234.788) l.name 'Delhi' l.longitude 112.345 l.latitude 234.788 l.name = "Kolkata"

Nguồn: https://realpython.com/python-data-groupes/


0

Một cách tiếp cận khác là tạo ra một trình bao bọc làm cho một thể hiện không thay đổi.

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)

Điều này hữu ích trong các tình huống chỉ một số trường hợp phải bất biến (như đối số mặc định của các lệnh gọi hàm).

Cũng có thể được sử dụng trong các nhà máy bất biến như:

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

Cũng bảo vệ khỏi object.__setattr__, nhưng có thể rơi vào các thủ thuật khác do bản chất năng động của Python.


0

Tôi đã sử dụng cùng một ý tưởng với Alex: một siêu lớp và một "điểm đánh dấu", nhưng kết hợp với viết quá __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)

Lưu ý: Tôi đang gọi trực tiếp lớp meta để làm cho nó hoạt động cả cho Python 2.x và 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.

Nó cũng hoạt động với các khe ...:

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

... và nhiều kế thừa:

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

Tuy nhiên, lưu ý rằng các thuộc tính có thể thay đổi vẫn có thể thay đổi:

>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]

0

Một điều không thực sự bao gồm ở đây là sự bất biến hoàn toàn ... không chỉ đối tượng cha mẹ, mà cả tất cả trẻ em. tuples / froundredet có thể là bất biến, nhưng các đối tượng mà nó là một phần của nó có thể không. Đây là một phiên bản nhỏ (chưa hoàn chỉnh) thực hiện tốt công việc bất biến mọi cách:

# 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

Bạn chỉ có thể ghi đè setAttr trong câu lệnh init cuối cùng. THen bạn có thể xây dựng nhưng không thay đổi. Rõ ràng bạn vẫn có thể ghi đè bằng đối tượng usint. setAttr nhưng trong thực tế hầu hết các ngôn ngữ đều có một số hình thức phản ánh nên tính bất biến luôn là một sự trừu tượng bị rò rỉ. Bất biến là nhiều hơn về việc ngăn chặn khách hàng vô tình vi phạm hợp đồng của một đối tượng. Tôi sử dụng:

=============================

Giải pháp ban đầu được cung cấp là không chính xác, điều này đã được cập nhật dựa trên các ý kiến ​​sử dụng giải pháp từ đây

Các giải pháp ban đầu là sai theo một cách thú vị, vì vậy nó được bao gồm ở phía dưới.

===============================

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 __name__ == "__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

Đầu ra:

1
2
Attempted To Modify Immutable Object
1
2

======================================

Thực hiện ban đầu:

Chính xác, nó đã được chỉ ra rằng các điều này thực tế không hoạt động, vì nó ngăn chặn việc tạo ra nhiều hơn một đối tượng khi bạn ghi đè phương thức setattr, có nghĩa là một giây không thể được tạo là self.a = will thất bại trong lần khởi tạo thứ hai.

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

1
Điều đó sẽ không hiệu quả: bạn đang ghi đè phương thức trên lớp , vì vậy bạn sẽ nhận được NotIm HiệnedError ngay khi bạn cố gắng tạo một phiên bản thứ hai.
slinkp

1
Nếu bạn muốn theo đuổi phương pháp này, lưu ý rằng thật khó để ghi đè các phương thức đặc biệt trong thời gian chạy: xem stackoverflow.com/a/16426447/137635 để biết một số cách giải quyết khác.
slinkp

0

Giải pháp cơ bản dưới đây giải quyết tình huống sau:

  • __init__() có thể được viết bằng cách truy cập các thuộc tính như bình thường.
  • SAU KHI ĐỐI TƯỢNG bị đóng băng chỉ thay đổi thuộc tính :

Ý tưởng là ghi đè __setattr__phương thức và thay thế cách thực hiện của nó mỗi khi thay đổi trạng thái đóng băng của đối tượng.

Vì vậy, chúng ta cần một số phương thức ( _freeze) lưu trữ hai triển khai và chuyển đổi giữa chúng khi được yêu cầu.

Cơ chế này có thể được thực hiện bên trong lớp người dùng hoặc được kế thừa từ một Freezerlớp đặc biệt như dưới đây:

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

Giống như một dict

Tôi có một thư viện mã nguồn mở nơi tôi đang làm mọi thứ theo cách có chức năng để việc di chuyển dữ liệu xung quanh trong một đối tượng không thay đổi là hữu ích. Tuy nhiên, tôi không muốn phải chuyển đổi đối tượng dữ liệu của mình để khách hàng tương tác với họ. Vì vậy, tôi đã đưa ra điều này - nó cung cấp cho bạn một đối tượng giống như đối tượng bất biến + một số phương thức trợ giúp.

Tín dụng cho Sven Marnach trong câu trả lời của ông cho việc thực hiện cơ bản là hạn chế cập nhật và xóa tài sản.

import json 
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out

class Immutable(object):

    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k,v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')

    def __getitem__(self, item):
        """Allows for dict like access of properties
        val = item['prop']
        """
        return self.__dict__[item]

    def __repr__(self):
        """Print to repl in a dict like fashion"""
        return self.pprint()

    def __str__(self):
        """Convert to a str in a dict like fashion"""
        return self.pprint()

    def __eq__(self, other):
        """Supports equality operator
        immutable({'a': 2}) == immutable({'a': 2})"""
        if other is None:
            return False
        return self.dict() == other.dict()

    def keys(self):
        """Paired with __getitem__ supports **unpacking
        new = { **item, **other }
        """
        return self.__dict__.keys()

    def get(self, *args, **kwargs):
        """Allows for dict like property access
        item.get('prop')
        """
        return self.__dict__.get(*args, **kwargs)

    def pprint(self):
        """Helper method used for printing that
        formats in a dict like way
        """
        return json.dumps(self,
            default=lambda o: o.__dict__,
            sort_keys=True,
            indent=4)

    def dict(self):
        """Helper method for getting the raw dict value
        of the immutable object"""
        return self.__dict__

Phương pháp trợ giúp

def update(obj, **kwargs):
    """Returns a new instance of the given object with
    all key/val in kwargs set on it
    """
    return immutable({
        **obj,
        **kwargs
    })

def immutable(obj):
    return Immutable(**obj)

Ví dụ

obj = immutable({
    'alpha': 1,
    'beta': 2,
    'dalet': 4
})

obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2

del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError

new_obj = update(obj, alpha=10)

new_obj is not obj # True
new_obj.get('alpha') == 10 # True
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.