Python'da değişmez bir nesne nasıl yapılır?


181

Buna hiç ihtiyaç duymama rağmen, Python'da değişmez bir nesne yapmanın biraz zor olabileceği beni vurdu. Sadece geçersiz kılamazsınız __setattr__, çünkü o zaman içinde özellikleri ayarlayamazsınız __init__. Bir grubun alt sınıflaması, çalışan bir numaradır:

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

Ama sonra ave bdeğişkenlerine self[0]ve aracılığıyla erişebilirsiniz self[1], bu da can sıkıcı bir durumdur.

Saf Python'da bu mümkün mü? Değilse, bunu bir C uzantısıyla nasıl yaparım?

(Yalnızca Python 3'te çalışan yanıtlar kabul edilebilir).

Güncelleme:

Yani, alt sınıf tuple Saf Python bunu yapmanın yoludur, bu da verilere [0], [1]vb . İle ek erişim olasılığı dışında iyi çalışır . Şüpheliyim, basit, geititemya da setattributevb. Uygulamadan . Ama kendim yapmak yerine, bunun için bir ödül teklif ediyorum, çünkü tembelim. :)


2
Kodunuz özniteliklere .ave yoluyla erişimi kolaylaştırmıyor .bmu? Sonuçta bu özelliklerin var olduğu görülmektedir.
Sven Marnach

1
@Ven Marnach: Evet, ama [0] ve [1] hala çalışıyor ve neden işe yararlar? Onları istemiyorum. :) Belki nitelikleri olan değişmez bir nesne fikri saçmadır? :-)
Lennart Regebro

2
Başka bir not: NotImplementedsadece zengin karşılaştırmalar için bir dönüş değeri anlamına gelir. Bir dönüş değeri __setatt__()zaten anlamsızdır, çünkü genellikle hiç görmezsiniz. Gibi kod immutable.x = 42sessizce hiçbir şey yapmaz. Onun TypeErroryerine bir yükseltmelisin .
Sven Marnach

1
@ Mar Marach: Tamam, şaşırdım, çünkü bu durumda NotImplemented'i yükseltebileceğini düşündüm, ama bu garip bir hata veriyor. Bu yüzden onu geri verdim ve işe yaramış gibi görünüyordu. TypeError, onu kullandığınızı gördükten sonra mantıklı geldi.
Lennart Regebro

1
@Lennart: Yükseltebilirsiniz NotImplementedError, ancak TypeErrordeğiştirmeye çalışırsanız bir demet yükselir.
Sven Marnach

Yanıtlar:


115

Düşündüğüm başka bir çözüm: Orijinal kodunuzla aynı davranışı elde etmenin en basit yolu

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

Bu nitelikler üzerinden erişilebilir sorunu çözmez [0]vb ama en azından oldukça kısa olduğunu ve uyumlu olma avantajına da sağlar pickleve copy.

namedtuplebu cevapta tarif ettiğim şeye benzer bir tür yaratır , yani türetilmiş tupleve kullanılarak __slots__. Python 2.6 veya üstü sürümlerde mevcuttur.


7
Bu varyantın elle yazılmış analoga kıyasla avantajı (Python 2.5'te bile ( koda verboseparametre kullanarak namedtuplekolayca oluşturulur)) namedtuplea'nın tek arayüzü / uygulaması, düzinelerce çok az farklı elle yazılmış arayüze / uygulamaya tercih edilir. do neredeyse aynı şeyi.
jfs

2
Tamam, "en iyi yanıtı" alırsınız, çünkü bunu yapmanın en kolay yolu budur. Sebastian, kısa bir Cython uygulaması verdiği için ödül alır. Şerefe!
Lennart Regebro

1
Değişmez cisimlerin bir başka özelliği de, onları bir fonksiyondan parametre olarak geçirdiğinizde, başka bir referanstan ziyade değere göre kopyalanmasıdır. Misiniz namedtuplefonksiyonları geçerken değeri tarafından kopyalanabilir s?
hlin117

4
@ hlin117: Her parametre, değiştirilebilir ya da değiştirilemez olmasına bakılmaksızın Python'daki bir nesneye başvuru olarak geçirilir. Değişmez nesneler için, bir kopya yapmak özellikle anlamsız olacaktır - nesneyi yine de değiştiremediğiniz için, orijinal nesneye bir referans da iletebilirsiniz.
Sven Marnach

Nesneyi harici olarak başlatmak yerine, adlandırılmış sınıfı sınıf içinde kullanabilir miyim? Python için çok yeniyim ama diğer cevabınızın avantajı, bir sınıfı ayrıntıları gizleyebilmem ve isteğe bağlı parametreler gibi şeylerin gücüne sahip olabilmem. Sadece bu cevaba bakarsam, sınıf örneğimi tuples adında kullanan her şeye sahip olmam gerekiyor gibi görünüyor. Her iki cevap için de teşekkürler.
Asaf

78

Bunu yapmanın en kolay yolu __slots__:

class A(object):
    __slots__ = []

AOnların özelliklerini ayarlayamayacağınız için, örnekleri şimdi değiştirilemez.

Sınıf örneklerinin veri içermesini istiyorsanız, bunu aşağıdakilerden türeterek birleştirebilirsiniz 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

Düzenleme : Dizin oluşturmadan da kurtulmak istiyorsanız, geçersiz kılabilirsiniz __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

Bunun yerine operator.itemgettergüveneceğinden, bu vakadaki özellikler için kullanamayacağınızı unutmayın . Fuerthermore, bunun kullanımını engellemez , ancak bunun nasıl bir sorun oluşturması gerektiğini hayal bile edemiyorum.Point.__getitem__()tuple.__getitem__()tuple.__getitem__(p, 0)

Değişmez bir nesne yaratmanın "doğru" yolunun bir C uzantısı yazmak olduğunu düşünmüyorum. Python genellikle kütüphane uygulayıcılarına ve kütüphane kullanıcılarına yetişkinlerin rıza göstermesine dayanır ve bir arayüzü gerçekten zorlamak yerine, arayüz belgelerde açıkça belirtilmelidir. Bu yüzden bir sorunu __setattr__()çağırarak geçersiz kılmayı atlatma olasılığını düşünmüyorum object.__setattr__(). Birisi bunu yaparsa, kendi riski altındadır.


1
tupleBurada kullanmak __slots__ = ()yerine , daha iyi bir fikir olmaz mıydı __slots__ = []? (Sadece
açıklayıcı

1
@sukhbir: Bence bu hiç önemli değil. Neden bir tuple tercih edersin?
Sven Marnach

1
@Sven: Bunun önemli olmayacağına katılıyorum (göz ardı edebileceğimiz hız kısmı hariç), ama bu şekilde düşündüm: __slots__değiştirilmeyecek mi? Amacı, hangi özelliklerin bir kez ayarlanabileceğini tanımlamaktır. Yani bir değil tuplebir çok görünüyor doğal böyle bir durumda seçim?
user225312

5
Ama boş olan __slots__ben ayarlayamıyor herhangi özelliklerini. Ve eğer __slots__ = ('a', 'b')o zaman a ve b nitelikleri hala değişebilir.
Lennart Regebro

Ancak çözümünüz geçersiz kılmaktan daha iyidir, __setattr__bu yüzden benimki üzerinde bir gelişme. +1 :)
Lennart Regebro

50

.. nasıl "düzgün" C yapmak ..

Python için bir uzantı türü oluşturmak için Cython'u kullanabilirsiniz :

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

Hem Python 2.x hem de 3'te çalışır.

Testler

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

Endeksleme desteğine aldırmazsanız , @Sven Marnachcollections.namedtuple tarafından önerilen tercih edilir:

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

@Lennart: namedtuple(veya işlev tarafından döndürülen türün örneklerinin namedtuple()) örnekleri değiştirilemez. Kesinlikle.
Sven Marnach

@Lennart Regebro: namedtupletüm testleri geçer (indeksleme desteği hariç). Hangi gereksinimi kaçırdım?
jfs

Evet, haklısın, adlandırılmış bir tür türü yaptım, somutlaştırdım ve sonra örnek yerine tür üzerinde test yaptım. Heh. :-)
Lennart Regebro

burada neden zayıf referansa ihtiyaç duyulduğunu sorabilir miyim?
McSinyx

1
@McSinyx: Aksi takdirde, nesneler zayıfref koleksiyonlarında kullanılamaz. Python'da tam olarak nedir __weakref__?
jfs

40

Başka bir fikir , yapıcıda tamamen izin vermemek __setattr__ve kullanmak olacaktır 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

Elbette object.__setattr__(p, "x", 3)bir Pointörneği değiştirmek için kullanabilirsiniz p, ancak orijinal uygulamanız aynı sorundan muzdariptir ( tuple.__setattr__(i, "x", 42)bir Immutableörneği deneyin ).

Aynı hileyi orijinal uygulamanıza da uygulayabilirsiniz: özellik işlevlerinden kurtulun __getitem__()ve kullanın tuple.__getitem__().


11
Birini kasıtlı olarak süper sınıf kullanarak nesneyi değiştirmeyi umursamıyorum ' __setattr__, çünkü nokta kusursuz değildir. Mesele, değiştirilmemesi gerektiğini açıkça belirtmek ve yanlışlıkla değiştirmeyi önlemektir.
zvone

18

@immutableGeçersiz kılan __setattr__ ve__slots__ boş bir listeyi değiştiren bir dekoratör oluşturabilir , ardından __init__yöntemi onunla dekore edebilirsiniz .

Düzenleme: OP'nin belirttiği gibi, __slots__özniteliğin değiştirilmesi, yalnızca yeni özniteliklerin oluşturulmasını engeller , değişikliği değil.

Edit2: İşte bir uygulama:

Edit3: Kullanarak __slots__bu kodu kırar, çünkü eğer nesnenin oluşturulmasını durdurur __dict__. Bir alternatif arıyorum.

Edit4: İşte bu kadar. Bu bir ama hackish, ama bir egzersiz olarak çalışır :-)

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
Çözeltiden (sınıf?) Bir dekoratör veya metasınıf yapmak gerçekten iyi bir fikirdir, ancak soru çözümün ne olduğudur. :)
Lennart Regebro

3
object.__setattr__()kırıyor stackoverflow.com/questions/4828080/…
jfs

Aslında. Dekoratörler üzerinde bir egzersiz yaptım.
PaoloVictor

13

Donmuş Veri Kümesi Kullanma

Python 3.7+ için, istediğinizi yapmak için çok pitonik ve bakımı kolay bir seçenek olan bir Veri Sınıfı kullanabilirsiniz .frozen=True

Bunun gibi bir şey olurdu:

from dataclasses import dataclass

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

As tipi ipucu gereklidir dataclasses' alanlar için, ben kullanmış gelen herhangi typingmodül .

Bir Adedtuple kullanmama nedenleri

Python 3.7'den önce adlandırılmış grupların değişmez nesneler olarak kullanıldığını görmek sıkça görülüyordu. Birçok yönden zor olabilir, bunlardan biri, __eq__namedtuples arasındaki yöntemin nesnelerin sınıflarını dikkate almamasıdır. Örneğin:

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

Gördüğünüz gibi, tipleri bile obj1ve obj2farklıdır, onların alanlar isimleri farklı olsa bile, obj1 == obj2yine de verir True. Bunun nedeni, __eq__kullanılan yöntemin, yalnızca konumlarının verildiği alanların değerlerini karşılaştıran demet olmasıdır. Bu, özellikle bu sınıfları alt sınıfa ayırıyorsanız, büyük bir hata kaynağı olabilir.


10

Bir grup ya da adlandırılmış bir grup kullanmak dışında bunun tamamen mümkün olduğunu düşünmüyorum. Ne olursa olsun, __setattr__()kullanıcıyı geçersiz kılarsanız , object.__setattr__()doğrudan arayarak her zaman atlayabilirsiniz . Herhangi bir çözümün __setattr__çalışmama garantisi vardır.

Aşağıdakiler, bir çeşit demet kullanmadan alabileceğiniz en yakın şey hakkındadır:

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__

ama yeterince denersen dener:

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

ama Sven'in kullanımı namedtuplegerçekten değişmez.

Güncelleme

Soru, C'de düzgün bir şekilde nasıl yapılacağını soracak şekilde güncellendiğinden, Cython'da nasıl doğru bir şekilde yapılacağına dair cevabım:

İlk olarak 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)

ve a setup.pyderlemek için (komutu kullanarak 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
)

Sonra denemek için:

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

Cython kodu için teşekkürler, Cython harika. JF Sebastians salt okunur ile uygulama daha temiz ve ilk geldi, bu yüzden o ödül alır.
Lennart Regebro

5

Ben geçersiz sınıflar yaptım __setattr__ve arayan ise set izin vererek __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)

Bu henüz yeterli değil, çünkü herhangi birinin ___init__nesneyi değiştirmesine izin veriyor , ancak fikri anlıyorsunuz.


object.__setattr__()kırıyor stackoverflow.com/questions/4828080/…
jfs

3
Arayanın olmadığından emin olmak için yığın denetimini kullanmak __init__çok tatmin edici değildir.
gb.

5

Diğer mükemmel cevaplara ek olarak python 3.4 (veya belki 3.3) için bir yöntem eklemeyi seviyorum. Bu cevap, bu soruya verilen birkaç önceki cevap üzerine kuruludur.

Python 3.4'te, değiştirilemeyen sınıf üyeleri oluşturmak için ayarlayıcılar olmadan özellikleri kullanabilirsiniz . (Önceki sürümlerde, ayarlayıcı olmadan özelliklere atama yapmak mümkündür.)

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

Bu şekilde kullanabilirsiniz:

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

hangisi yazdıracak "constant"

Ancak arama instance.a=10:

AttributeError: can't set attribute

Açıklama: ayarlayıcısız özellikler python 3.4'ün (ve sanırım 3.3) çok yeni bir özelliğidir. Böyle bir mülke atamayı denerseniz, bir Hata ortaya çıkar. Yuvaları kullanarak üye değişkenlerini __A_a(ki __a) ile kısıtlıyorum .

Sorun: Atama işlemi _A__ahala mümkün ( instance._A__a=2). Ama özel bir değişkene atarsanız, bu sizin kendi hatanızdır ...

Ancak bu cevap diğerleri arasında kullanımını engelliyor __slots__. Özellik oluşturmayı önlemek için başka yollar kullanmak tercih edilebilir.


propertyPython 2'de de mevcuttur (sorunun kendisindeki koda bakın). Değişmez bir nesne yaratmaz, cevabımdaki testleri deneyin , örneğin instance.b = 1yeni bir bözellik oluşturur .
jfs

Doğru, soru gerçekten yapmanın nasıl önleneceği, A().b = "foo"yani yeni özelliklerin ayarlanmasına izin vermemektir.
Lennart Regebro

Ayarlayıcı olmadan Propertis, bu özelliğe atamaya çalışırsanız python 3.4'te bir hata oluşturur. Önceki versiyonlarda, ayarlayıcı sorunsuz bir şekilde oluşturulmuştur.
Bernhard

@Lennart: Çözümüm, değiştirilemeyen nesneler için kullanım senaryolarının bir alt kümesine bir yanıt ve önceki yanıtlara bir ektir. Değişmez bir nesne istemememin bir nedeni, onu yıkanabilir yapabilmemdir, bu durumda çözümüm işe yarayabilir. Ama haklısın, bu değişmez bir nesne değil.
Bernhard

@ jf-sebastian: Yanıtımı öznitelik oluşturmayı önlemek için yuva kullanmak için değiştirdim. Cevabımda diğer cevaplara göre yeni olan şey, varolan öznitelikleri değiştirmekten kaçınmak için python3.4'ün özelliklerini kullanmam. Previose cevaplarda da aynı şekilde elde edilirken, özelliklerin davranışındaki değişiklik nedeniyle kodum daha kısadır.
Bernhard

5

İşte zarif bir çözüm:

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

Bu sınıftan devralın, kurucudaki alanlarınızı başlatın ve hazırsınız.


1
ancak bu mantıkla nesneye yeni özellikler atamak mümkün
javed

3

Davranışı olan nesnelerle ilgileniyorsanız, namedtuple neredeyse çözümünüzdür.

Adlandırılmış kayıt belgelerinin alt kısmında açıklandığı gibi , kendi sınıfınızı adlandırılmış gruptan türetebilirsiniz; ve sonra istediğiniz davranışı ekleyebilirsiniz.

Örneğin (kod doğrudan dokümantasyondan alınmıştır ):

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)

Bunun sonucu:

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

Bu yaklaşım hem Python 3 hem de Python 2.7 için geçerlidir (IronPython üzerinde de test edilmiştir).
Tek dezavantajı, miras ağacının biraz garip olmasıdır; ama bu genellikle oynadığınız bir şey değil.


1
Python 3.6+ bunu kullanarak doğrudan destekliyorclass Point(typing.NamedTuple):
Elazar

3

Aşağıdaki Immutablesınıftan miras alan sınıflar , __init__yöntemlerinin yürütülmesi bittikten sonra, örnekleri gibi değişmezdir . Saf python olduğundan, diğerlerinin de belirttiği gibi, birisinin tabandan mutasyon yapan özel yöntemleri kullanmasını engelleyen hiçbir şey yoktur objectve typebu, herhangi bir kişinin yanlışlıkla bir sınıfı / örneği mutasyona uğratmasını durdurmak için yeterlidir.

Sınıf yaratma sürecini bir metasınıfla ele geçirerek çalışır.

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

Biraz önce buna ihtiyacım vardı ve bunun için bir Python paketi yapmaya karar verdim. İlk sürüm şimdi PyPI'de:

$ pip install immutable

Kullanmak:

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

Tam dokümanlar burada: https://github.com/theengineear/immutable

Umarım yardımcı olur, tartışıldığı gibi adlandırılmış bir paketi sarar, ancak örneklemeyi çok daha basit hale getirir.


2

Bu şekilde object.__setattr__çalışmak durmuyor , ancak yine de yararlı buldum:

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__kullanım durumuna bağlı olarak daha fazla öğeyi (gibi ) geçersiz kılmanız gerekebilir .


Bunu görmeden önce benzer bir şey buldum, ancak kullanılan getattrbir varsayılan değer sağlayabilir frozen. Bu işleri biraz basitleştirdi. stackoverflow.com/a/22545808/5987
Mark Ransom

Bu yaklaşımı en çok sevdim, ancak __new__geçersiz kılmaya ihtiyacınız yok . İçinde __setattr__sadece koşullu yerineif name != '_frozen' and getattr(self, "_frozen", False)
Pete Cacioppi

Ayrıca, inşaat sırasında sınıfı dondurmaya gerek yoktur. Bir freeze()işlev sağlarsanız, istediğiniz zaman donabilirsiniz . Nesne daha sonra "bir kez dondur" olacaktır. Son olarak, endişelenmek object.__setattr__saçmadır, çünkü "hepimiz burada yetişkiniz".
Pete Cacioppi

2

Python 3.7'den itibaren @dataclassdekoratörü sınıfınızda kullanabilirsiniz ve bir yapı gibi değişmez olacaktır! Yine de, __hash__()sınıfınıza bir yöntem ekleyebilir veya eklemeyebilir . Alıntı:

hash (), yerleşik hash () tarafından kullanılır ve sözlükler ve kümeler gibi karma koleksiyonlara nesneler eklendiğinde. Bir hash () öğesine sahip olmak, sınıf örneklerinin değişmez olduğunu ima eder. Değişebilirlik, programcının amacına, eq () varlığına ve davranışına ve dataclass () dekoratörünün eq ve donmuş bayraklarının değerlerine bağlı olan karmaşık bir özelliktir.

Varsayılan olarak, dataclass () yöntemi güvenli olmadığı sürece örtük olarak bir hash () yöntemi eklemez . Mevcut olarak açıkça tanımlanmış bir hash () yöntemini de eklemez veya değiştirmez . Class özniteliğinin ayarlanması hash = None, hash () belgelerinde açıklandığı gibi Python için özel bir anlama sahiptir .

Eğer karma () tanımlı açık değildir, ya da yok olarak ayarlanırsa, o zaman dataclass () Bir örtülü ekleyebilir karma () yöntemini. Tavsiye edilmese de, dasalass () yöntemini unsafe_hash = True ile bir hash () yöntemi oluşturmaya zorlayabilirsiniz. Sınıfınız mantıksal olarak değişmezse, ancak yine de mutasyona uğrayabiliyorsa, durum böyle olabilir. Bu özel bir kullanım durumudur ve dikkatle ele alınmalıdır.

Yukarıda bağlantılı dokümanlardan bir örnek:

@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
Kullanmak gerek frozenyani @dataclass(frozen=True), ama temelde blokları kullanımı __setattr__ve __delattr__burada diğer cevapların çoğunda gibi. Sadece diğer veri seçenekleriyle uyumlu bir şekilde yapar.
CS

2

Setattr değerini geçersiz kılabilir ve yine de değişkeni ayarlamak için init kullanabilirsiniz . Süper sınıf setattr kullanırsınız . İşte kod.

Sınıf değişmez:
    __slots__ = ('a', 'b')
    def __init __ (öz, a, b):
        super () .__ setattr __ ( 'a', a)
        super () .__ setattr __ ( 'b', b)

    def __str __ (öz):
        dönüş "" .format (self.a, self.b)

    def __setattr __ (öz, * yoksayıldı):
        yükseltmek NotImplementedError

    def __delattr __ (öz, * yoksayıldı):
        yükseltmek NotImplementedError

Veya sadece passyerineraise NotImplementedError
jonathan.scholbach

Bu durumda __setattr__ ve __delattr__ ile "pass" yapmak hiç de iyi bir fikir değil. Bunun basit nedeni, eğer birisi bir alana / mülke bir değer atarsa, doğal olarak alanın değiştirilmesini beklerler. "En az sürpriz" yolunu izlemek istiyorsanız (gerektiği gibi), bir hata ortaya çıkarmanız gerekir. Ancak NotImplementedError'ın yükseltmek için doğru olup olmadığından emin değilim. "Tarla / mülkiyet değişmez." hata ... Bence özel bir istisna atılmalıdır.
Darlove

1

Üçüncü taraf attrmodülü bu işlevi sağlar .

Düzenleme: python 3.7 ile stdlib içine bu fikri benimsemiştir @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

attrdondurulmuş sınıfları geçersiz kılarak uygular __setattr__ve belgelere göre her örnekleme zamanında küçük bir performans etkisi vardır.

Sınıfları veri türü olarak kullanma alışkanlığınız varsa, attrözellikle sizin için ısıtıcı levhaya baktığı için yararlı olabilir (ancak sihir yapmaz). Özellikle, repr, init, hash ve tüm karşılaştırma işlevleri de dahil olmak üzere sizin için dokuz dunder (__X__) yöntemi yazar (bunlardan herhangi birini kapatmazsanız).

attrayrıca bir yardımcı sağlar__slots__ .


1

Yani, ben python 3 ilgili yazıyorum:

I) veri sınıfı dekoratör yardımıyla ve set dondurulmuş = Doğru. python'da değişmez nesneler yaratabiliriz.

bu veri sınıfından veri sınıfını içe aktarmak için lib ve dondurulmuş olarak ayarlamanız gerekir = True

ex.

Dataclasses ithal Dataclass

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

o / p:

l = Konum ("Delhi", 112.345, 234.788) l.name 'Delhi' l.longitude 112.345 l.latitude 234.788 l.name = "Kolkata" dataclasses.FrozenInstanceError: 'name' alanına atanamıyor

Kaynak: https://realpython.com/python-data-classes/


0

Alternatif bir yaklaşım, bir örneği değişmez kılan bir sargı oluşturmaktır.

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)

Bu, yalnızca bazı örneklerin değiştirilemez olduğu durumlarda (işlev çağrılarının varsayılan argümanları gibi) kullanışlıdır.

Aşağıdaki gibi değişmez fabrikalarda da kullanılabilir:

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

Ayrıca object.__setattr__Python'un dinamik doğası nedeniyle diğer hilelerden korur , ancak taklit edilebilir.


0

Alex ile aynı fikri kullandım: bir meta sınıfı ve bir "init işaretleyicisi", ancak overse__ __setattr__ ile birlikte:

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

Not: Meta sınıfı doğrudan Python 2.x ve 3.x için çalışmasını sağlamak için arıyorum.

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

Yuvalarla da çalışır ...:

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

... ve çoklu kalıtım:

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

Bununla birlikte, değişebilir niteliklerin değişebileceğini unutmayın:

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

0

Burada gerçekten dahil olmayan bir şey, tamamen değişmezlik ... sadece ana nesne değil, aynı zamanda tüm çocuklar. tuples / frozensets örneğin değişmez olabilir, ancak parçası olduğu nesneler olmayabilir. İşte değişmezliği tamamen aşağıya çekmek için iyi bir iş yapan küçük (eksik) bir sürüm:

# 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

Son init deyiminde setAttr öğesini geçersiz kılabilirsiniz. O zaman inşa edebilirsin ama değiştiremezsin. Açıkçası usint nesnesi tarafından geçersiz kılabilirsiniz. setattr ama pratikte çoğu dilde bir tür yansıma vardır, bu yüzden değişmezlik her zaman sızdıran bir soyutlamadır. Değişmezlik, müşterilerin bir nesnenin sözleşmesini yanlışlıkla ihlal etmesini önlemekle ilgilidir. Kullanırım:

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

Sunulan orijinal çözüm, yanlış bundan çözümü kullanarak açıklamalara dayanarak güncellendi burada

Orijinal çözüm ilginç bir şekilde yanlış, bu nedenle altta yer alıyor.

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

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

Çıktı :

1
2
Attempted To Modify Immutable Object
1
2

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

Orijinal Uygulama:

Yorumlarda, sınıf setattr yöntemini geçersiz kıldıkça birden fazla nesnenin oluşturulmasını engellediği için bunun aslında işe yaramadığı belirtildi, bu da ikinci bir self.a = will olarak oluşturulamayacağı anlamına geliyor. ikinci başlatmada başarısız.

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
Bu işe yaramaz: sınıftaki yöntemi geçersiz kılarsınız, böylece ikinci bir örnek oluşturmaya çalıştığınızda NotImplementedError alırsınız.
23'te slinkp

1
Bu yaklaşımı izlemek istiyorsanız, çalışma zamanında özel yöntemleri geçersiz kılmanın zor olduğunu unutmayın: bunun için birkaç geçici çözüm için stackoverflow.com/a/16426447/137635 adresine bakın .
23'te slinkp

0

Aşağıdaki temel çözüm aşağıdaki senaryoyu ele almaktadır:

  • __init__() niteliklere her zamanki gibi erişerek yazılabilir.
  • NESNE yalnızca özellik değişiklikleri için dondurulduktan sonra :

Fikir, __setattr__yöntemi dondurmak ve nesne dondurulmuş durumu her değiştiğinde uygulanmasını değiştirmek.

Bu nedenle, _freezebu iki uygulamayı depolayan ve istendiğinde bunlar arasında geçiş yapan bir yönteme ( ) ihtiyacımız var .

Bu mekanizma, kullanıcı sınıfı içinde uygulanabilir veya Freezeraşağıda gösterildiği gibi özel bir sınıftan miras alınabilir :

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

Tıpkı bir dict

Değişken bir nesnede veri taşımak yararlı bir şeyleri işlevsel bir şekilde yaptığım bir açık kaynak kütüphanesi var . Ancak, istemcinin onlarla etkileşime girmesi için veri nesnesimi dönüştürmek istemiyorum. Yani, ben bunu buldum - bu değişmez bir nesne gibi bir diksiyon + bazı yardımcı yöntemler verir.

Kredi Sven Marnach onun içinde cevap mülkiyet güncellenmesi kısıtlayan ve silme temel uygulanması için.

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__

Yardımcı yöntemler

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)

Örnekler

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.