Meskipun saya tidak pernah membutuhkan ini, saya baru sadar bahwa membuat objek yang tidak dapat diubah dengan Python bisa sedikit rumit. Anda tidak bisa hanya menimpanya __setattr__
, karena Anda bahkan tidak dapat mengatur atribut di __init__
. Subkelas tuple adalah trik yang berfungsi:
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
Tapi kemudian Anda memiliki akses ke a
dan b
variabel melalui self[0]
dan self[1]
, yang menjengkelkan.
Apakah ini dimungkinkan dengan Pure Python? Jika tidak, bagaimana saya melakukannya dengan ekstensi C?
(Jawaban yang hanya berfungsi di Python 3 dapat diterima).
Memperbarui:
Jadi subclassing tuple adalah cara untuk melakukannya dalam Pure Python, yang berfungsi dengan baik kecuali untuk kemungkinan tambahan mengakses data dengan [0]
, [1]
dll. Jadi, untuk menyelesaikan pertanyaan ini semua yang hilang adalah bagaimana melakukannya dengan "benar" di C, yang saya menduga akan cukup sederhana, dengan hanya tidak melaksanakan setiap geititem
atau setattribute
, dll Tapi bukannya melakukan sendiri, saya menawarkan karunia untuk itu, karena aku malas. :)
sumber
.a
dan.b
? Lagipula itulah sifat dari properti itu.NotImplemented
hanya dimaksudkan sebagai nilai balik untuk perbandingan kaya. Nilai pengembalian__setatt__()
agak tidak berguna, karena Anda biasanya tidak akan melihatnya sama sekali. Kode sukaimmutable.x = 42
diam-diam tidak akan melakukan apa pun. Anda harus menaikkan sebagaiTypeError
gantinya.NotImplementedError
, tetapiTypeError
merupakan kenaikan tuple jika Anda mencoba memodifikasinya.Jawaban:
Namun solusi lain yang baru saja saya pikirkan: Cara paling sederhana untuk mendapatkan perilaku yang sama dengan kode asli Anda adalah
Itu tidak memecahkan masalah yang atribut dapat diakses melalui
[0]
dll, tapi setidaknya itu jauh lebih pendek dan memberikan keuntungan tambahan yang kompatibel denganpickle
dancopy
.namedtuple
menciptakan jenis yang mirip dengan apa yang saya jelaskan dalam jawaban ini , yaitu berasal darituple
dan menggunakan__slots__
. Ini tersedia dalam Python 2.6 atau lebih tinggi.sumber
verbose
parameter kenamedtuple
kode dengan mudah dihasilkan)) adalah antarmuka tunggal / implementasi anamedtuple
lebih disukai oleh puluhan antarmuka tulisan tangan yang sedikit berbeda / implementasi yang mengimplementasikan lakukan hal yang hampir sama.namedtuple
akan disalin oleh nilai ketika melewati fungsi?Cara termudah untuk melakukannya adalah menggunakan
__slots__
:Contoh dari
A
sekarang tidak dapat diubah, karena Anda tidak dapat menetapkan atribut apa pun padanya.Jika Anda ingin instance kelas berisi data, Anda dapat menggabungkan ini dengan berasal dari
tuple
:Sunting : Jika Anda ingin menyingkirkan pengindeksan, Anda dapat mengganti
__getitem__()
:Perhatikan bahwa Anda tidak dapat menggunakan
operator.itemgetter
untuk properti dalam hal ini, karena ini akan bergantung padaPoint.__getitem__()
bukantuple.__getitem__()
. Terlebih lagi ini tidak akan mencegah penggunaantuple.__getitem__(p, 0)
, tapi saya tidak bisa membayangkan bagaimana ini bisa menjadi masalah.Saya tidak berpikir cara "benar" untuk membuat objek yang tidak berubah adalah menulis ekstensi C. Python biasanya bergantung pada pelaksana perpustakaan dan pengguna perpustakaan yang menyetujui orang dewasa , dan bukannya benar-benar menegakkan antarmuka, antarmuka harus dinyatakan dengan jelas dalam dokumentasi. Inilah sebabnya saya tidak mempertimbangkan kemungkinan menghindari ditimpa
__setattr__()
dengan memanggilobject.__setattr__()
masalah. Jika seseorang melakukan ini, itu adalah risikonya sendiri.sumber
tuple
sini__slots__ = ()
, bukan__slots__ = []
? (Hanya mengklarifikasi)__slots__
tidak akan diubah, kan? Tujuannya adalah untuk mengidentifikasi sekali atribut mana yang dapat ditetapkan. Jadi tidak seorangtuple
tampak lebih alami pilihan dalam kasus seperti itu?__slots__
saya tidak dapat mengatur atribut apa pun . Dan jika saya punya__slots__ = ('a', 'b')
maka atribut a dan b masih bisa berubah.__setattr__
jadi ini lebih baik daripada solusi saya. +1 :)Anda bisa menggunakan Cython untuk membuat tipe ekstensi untuk Python:
Ini berfungsi baik Python 2.x dan 3.
Tes
Jika Anda tidak keberatan dengan dukungan pengindeksan maka
collections.namedtuple
disarankan oleh @ven Marnach lebih disukai :sumber
namedtuple
(atau lebih tepatnya dari jenis yang dikembalikan oleh fungsinamedtuple()
) tidak berubah. Pastinya.namedtuple
lulus semua tes (kecuali dukungan pengindeksan). Persyaratan apa yang saya lewatkan?__weakref__
di Python?Gagasan lain adalah sepenuhnya tidak diizinkan
__setattr__
dan digunakanobject.__setattr__
dalam konstruktor:Tentu saja Anda dapat menggunakan
object.__setattr__(p, "x", 3)
untuk memodifikasi sebuahPoint
instancep
, tetapi implementasi asli Anda mengalami masalah yang sama (cobatuple.__setattr__(i, "x", 42)
pada sebuahImmutable
instance).Anda dapat menerapkan trik yang sama dalam implementasi asli Anda: singkirkan
__getitem__()
, dan gunakantuple.__getitem__()
dalam fungsi properti Anda.sumber
__setattr__
, karena intinya bukan untuk menjadi sangat mudah. Intinya adalah untuk memperjelas bahwa itu tidak boleh dimodifikasi dan untuk mencegah modifikasi secara tidak sengaja.Anda bisa membuat
@immutable
dekorator yang menimpa__setattr__
dan mengubah__slots__
ke daftar kosong, lalu hiasi__init__
metode dengan itu.Sunting: Seperti yang dicatat OP, mengubah
__slots__
atribut hanya mencegah pembuatan atribut baru , bukan modifikasi.Edit2: Berikut ini implementasinya:
Sunting3: Menggunakan
__slots__
istirahat kode ini, karena jika menghentikan pembuatan objek__dict__
. Saya mencari alternatif.Edit4: Yah, itu dia. Ini tapi retas, tetapi berfungsi sebagai latihan :-)
sumber
object.__setattr__()
istirahatkan stackoverflow.com/questions/4828080/…Menggunakan Dataclass Beku
Untuk Python 3.7+ Anda dapat menggunakan Kelas Data dengan
frozen=True
opsi , yang merupakan cara yang sangat pythonic dan dapat dipelihara untuk melakukan apa yang Anda inginkan.Itu akan terlihat seperti itu:
Karena petunjuk jenis diperlukan untuk bidang dataclasses, saya telah menggunakan Any dari
typing
modul .Alasan untuk TIDAK menggunakan Namedtuple
Sebelum Python 3.7, sering kali melihat namesuple digunakan sebagai objek yang tidak berubah. Ini bisa rumit dalam banyak hal, salah satunya adalah bahwa
__eq__
metode antara namesuple tidak mempertimbangkan kelas objek. Sebagai contoh:Seperti yang Anda lihat, bahkan jika jenis
obj1
danobj2
berbeda, bahkan jika nama bidangnya berbeda,obj1 == obj2
tetap memberiTrue
. Itu karena__eq__
metode yang digunakan adalah metode tuple, yang hanya membandingkan nilai bidang yang diberikan posisi mereka. Itu bisa menjadi sumber kesalahan besar, khususnya jika Anda mensubkelas kelas-kelas ini.sumber
Saya tidak berpikir itu sepenuhnya mungkin kecuali dengan menggunakan tuple atau namesuple. Apa pun itu, jika Anda menimpa
__setattr__()
, pengguna selalu dapat memintasnya dengan meneleponobject.__setattr__()
langsung. Solusi apa pun yang bergantung pada__setattr__
dijamin tidak akan berfungsi.Berikut ini tentang yang terdekat yang bisa Anda dapatkan tanpa menggunakan semacam tuple:
tetapi rusak jika Anda berusaha cukup keras:
tapi penggunaan Sven untuk
namedtuple
benar-benar abadi.Memperbarui
Karena pertanyaan telah diperbarui untuk menanyakan bagaimana melakukannya dengan benar di C, inilah jawaban saya tentang bagaimana melakukannya dengan benar di Cython:
Pertama
immutable.pyx
:dan a
setup.py
untuk mengkompilasinya (menggunakan perintahsetup.py build_ext --inplace
:Kemudian untuk mencobanya:
sumber
Saya telah membuat kelas yang tidak dapat diubah dengan mengesampingkan
__setattr__
, dan mengizinkan set jika pemanggilnya adalah__init__
:Ini belum cukup, karena memungkinkan siapa pun
___init__
untuk mengubah objek, tetapi Anda mendapatkan idenya.sumber
object.__setattr__()
istirahatkan stackoverflow.com/questions/4828080/…__init__
tidak terlalu memuaskan.Selain jawaban lain yang sangat baik saya ingin menambahkan metode untuk python 3.4 (atau mungkin 3.3). Jawaban ini dibangun berdasarkan beberapa jawaban sebelumnya untuk pertanyaan ini.
Di python 3.4, Anda bisa menggunakan properti tanpa setter untuk membuat anggota kelas yang tidak bisa dimodifikasi. (Dalam versi sebelumnya, menugaskan ke properti tanpa setter dimungkinkan.)
Anda bisa menggunakannya seperti ini:
yang akan dicetak
"constant"
Tetapi panggilan
instance.a=10
akan menyebabkan:Penjelasan: properti tanpa setter adalah fitur yang sangat baru dari python 3.4 (dan saya pikir 3.3). Jika Anda mencoba untuk menetapkan properti seperti itu, sebuah Kesalahan akan muncul. Menggunakan slot, saya membatasi variabel member untuk
__A_a
(yang__a
).Masalah: Menugaskan
_A__a
masih dimungkinkan (instance._A__a=2
). Tetapi jika Anda menetapkan ke variabel pribadi, itu adalah kesalahan Anda sendiri ...Namun, jawaban ini antara lain tidak mendukung penggunaan
__slots__
. Menggunakan cara lain untuk mencegah pembuatan atribut mungkin lebih disukai.sumber
property
tersedia di Python 2 juga (lihat kode dalam pertanyaan itu sendiri). Itu tidak membuat objek abadi, coba tes dari jawaban saya , misalnya,instance.b = 1
membuatb
atribut baru .A().b = "foo"
yaitu tidak mengizinkan pengaturan atribut baru.Inilah solusi elegan :
Mewarisi dari kelas ini, inisialisasi bidang Anda di konstruktor, dan Anda siap.
sumber
Jika Anda tertarik pada objek dengan perilaku, maka namesuple hampir menjadi solusi Anda.
Seperti dijelaskan di bagian bawah dokumentasi namedtuple , Anda dapat menurunkan kelas Anda sendiri dari namedtuple; dan kemudian, Anda bisa menambahkan perilaku yang Anda inginkan.
Misalnya (kode diambil langsung dari dokumentasi ):
Ini akan menghasilkan:
Pendekatan ini berfungsi untuk Python 3 dan Python 2.7 (diuji pada IronPython juga).
Satu-satunya downside adalah bahwa pohon warisan agak aneh; tapi ini bukan sesuatu yang biasa kamu mainkan.
sumber
class Point(typing.NamedTuple):
Kelas yang mewarisi dari
Immutable
kelas berikut tidak dapat diubah, seperti contoh mereka, setelah__init__
metode mereka selesai dijalankan. Karena itu adalah python murni, seperti yang telah ditunjukkan orang lain, tidak ada yang menghentikan seseorang untuk menggunakan metode khusus bermutasi dari basisobject
dantype
, tetapi ini cukup untuk menghentikan siapa pun dari bermutasi kelas / instance secara tidak sengaja.Ini bekerja dengan membajak proses pembuatan kelas dengan metaclass.
sumber
Saya membutuhkan ini beberapa waktu yang lalu dan memutuskan untuk membuat paket Python untuk itu. Versi awal ada di PyPI sekarang:
Menggunakan:
Dokumentasi lengkap di sini: https://github.com/theengineear/immutable
Semoga ini bisa membantu, ia membungkus sebuah namesuple seperti yang telah dibahas, tetapi membuat instantiasi menjadi lebih sederhana.
sumber
Cara ini tidak berhenti
object.__setattr__
bekerja, tetapi saya masih merasa berguna:Anda mungkin perlu mengganti lebih banyak barang (seperti
__setitem__
) tergantung pada use case.sumber
getattr
sehingga saya bisa memberikan nilai default untukfrozen
. Hal-hal yang disederhanakan sedikit. stackoverflow.com/a/22545808/5987__new__
menimpanya. Di dalam__setattr__
hanya mengganti kondisi denganif name != '_frozen' and getattr(self, "_frozen", False)
freeze()
fungsi. Objek kemudian akan "dibekukan sekali". Akhirnya, mengkhawatirkanobject.__setattr__
itu konyol, karena "kita semua dewasa di sini".Pada Python 3.7, Anda dapat menggunakan
@dataclass
dekorator di kelas Anda dan itu tidak akan berubah seperti struct! Meskipun, itu mungkin atau mungkin tidak menambahkan__hash__()
metode ke kelas Anda. Mengutip:Berikut ini contoh dari dokumen yang ditautkan di atas:
sumber
frozen
, yaitu@dataclass(frozen=True)
, tetapi pada dasarnya memblokir penggunaan__setattr__
dan__delattr__
suka di sebagian besar jawaban lain di sini. Itu hanya melakukannya dengan cara yang kompatibel dengan opsi lain dari kacamata.Anda bisa mengesampingkan setattr dan masih menggunakan init untuk mengatur variabel. Anda akan menggunakan kelas super setattr . ini kodenya.
sumber
pass
alih-alihraise NotImplementedError
attr
Modul pihak ketiga menyediakan fungsionalitas ini .Sunting: python 3.7 telah mengadopsi gagasan ini ke dalam stdlib with
@dataclass
.attr
mengimplementasikan kelas beku dengan mengesampingkan__setattr__
dan memiliki dampak kinerja kecil pada setiap waktu instantiation, menurut dokumentasi.Jika Anda terbiasa menggunakan kelas sebagai tipe data,
attr
mungkin sangat berguna karena menangani boilerplate untuk Anda (tetapi tidak melakukan sihir apa pun). Secara khusus, ia menulis sembilan metode dunder (__X__) untuk Anda (kecuali Anda mematikannya), termasuk repr, init, hash dan semua fungsi perbandingan.attr
juga menyediakan pembantu untuk__slots__
.sumber
Jadi, saya menulis masing-masing python 3:
I) dengan bantuan dekorator kelas data dan set beku = True. kita dapat membuat objek yang tidak dapat diubah dengan python.
untuk ini perlu mengimpor data kelas dari kelas data lib dan perlu menetapkan beku = True
ex.
dari dataclasses impor dataclass
o / p:
Sumber: https://realpython.com/python-data-classes/
sumber
Pendekatan alternatif adalah membuat pembungkus yang membuat instance menjadi tidak dapat diubah.
Ini berguna dalam situasi di mana hanya beberapa instance harus tidak berubah (seperti argumen default panggilan fungsi).
Dapat juga digunakan di pabrik yang tidak berubah seperti:
Juga melindungi dari
object.__setattr__
, tetapi jatuh ke trik lain karena sifat dinamis Python.sumber
Saya menggunakan ide yang sama dengan Alex: kelas-meta dan "init marker", tetapi dalam kombinasi dengan __setattr__ yang ditulis berlebihan:
Catatan: Saya memanggil meta-class secara langsung untuk membuatnya berfungsi baik untuk Python 2.x dan 3.x.
Ini juga berfungsi dengan slot ...:
... dan banyak warisan:
Perhatikan, bagaimanapun, atribut yang bisa berubah tetap dapat berubah:
sumber
Satu hal yang tidak benar-benar dimasukkan di sini adalah total immutability ... bukan hanya objek induk, tetapi semua anak juga. tuple / frozenset mungkin tidak dapat diubah misalnya, tetapi objek yang menjadi bagiannya mungkin tidak berubah. Berikut adalah versi kecil (tidak lengkap) yang melakukan pekerjaan yang layak untuk menegakkan keabadian sepanjang jalan:
sumber
Anda bisa mengesampingkan setAttr dalam pernyataan akhir init. Kemudian Anda dapat membangun tetapi tidak berubah. Jelas Anda masih bisa menimpa oleh objek usint. setAttr tetapi dalam praktiknya sebagian besar bahasa memiliki beberapa bentuk refleksi sehingga ketidakmampuan selalu merupakan abstraksi yang bocor. Kekekalan lebih lanjut tentang mencegah klien dari secara tidak sengaja melanggar kontrak suatu objek. Saya menggunakan:
=============================
Solusi asli yang ditawarkan salah, ini diperbarui berdasarkan komentar menggunakan solusi dari sini
Solusi asli salah dengan cara yang menarik, sehingga termasuk di bagian bawah.
===============================
Output:
====================================
Implementasi Asli:
Itu ditunjukkan dalam komentar, dengan benar, bahwa ini sebenarnya tidak berhasil, karena mencegah penciptaan lebih dari satu objek saat Anda mengganti metode setattr kelas, yang berarti yang kedua tidak dapat dibuat sebagai self.a = akan gagal pada inisialisasi kedua.
sumber
Solusi dasar di bawah ini membahas skenario berikut:
__init__()
dapat ditulis mengakses atribut seperti biasa.Idenya adalah untuk mengganti
__setattr__
metode dan mengganti implementasinya setiap kali status beku objek diubah.Jadi kita memerlukan beberapa metode (
_freeze
) yang menyimpan dua implementasi dan sakelar di antara keduanya saat diminta.Mekanisme ini dapat diterapkan di dalam kelas pengguna atau diwarisi dari
Freezer
kelas khusus seperti yang ditunjukkan di bawah ini:sumber
Seperti a
dict
Saya memiliki perpustakaan open source di mana saya melakukan hal-hal dengan cara fungsional sehingga memindahkan data di dalam objek yang tidak berubah sangat membantu. Namun, saya tidak ingin harus mengubah objek data saya agar klien dapat berinteraksi dengan mereka. Jadi, saya datang dengan ini - ini memberi Anda dict seperti objek thats abadi + beberapa metode pembantu.
Penghargaan untuk Sven Marnach dalam jawabannya atas implementasi dasar pembatasan pembaruan dan penghapusan properti.
Metode pembantu
Contohnya
sumber