Gunakan Metaclass
Saya akan merekomendasikan Metode # 2 , tetapi Anda lebih baik menggunakan metaclass daripada kelas dasar. Berikut ini contoh implementasi:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(object):
__metaclass__ = Singleton
Atau dalam Python3
class Logger(metaclass=Singleton):
pass
Jika Anda ingin menjalankan __init__
setiap kali kelas dipanggil, tambahkan
else:
cls._instances[cls].__init__(*args, **kwargs)
pada if
pernyataan dalam Singleton.__call__
.
Beberapa kata tentang metaclasses. Metaclass adalah kelas suatu kelas ; yaitu, kelas adalah turunan dari metaclassnya . Anda menemukan metaclass dari objek dengan Python type(obj)
. Kelas gaya baru normal adalah tipe type
. Logger
dalam kode di atas akan bertipe class 'your_module.Singleton'
, sama seperti (hanya) turunan Logger
bertipe class 'your_module.Logger'
. Ketika Anda menelepon logger dengan Logger()
, Python pertama meminta metaclass dari Logger
, Singleton
, apa yang harus dilakukan, sehingga contoh penciptaan untuk pra-empted. Proses ini sama dengan Python yang meminta kelas apa yang harus dilakukan dengan memanggil __getattr__
ketika Anda mereferensikan salah satu atributnya dengan melakukan myclass.attribute
.
Metaclass pada dasarnya memutuskan apa arti suatu kelas dan bagaimana mengimplementasikannya. Lihat misalnya http://code.activestate.com/recipes/498149/ , yang pada dasarnya menciptakan ulang gaya-C struct
di Python menggunakan metaclasses. Utas Apa sajakah case penggunaan (konkret) untuk metaclasses? juga memberikan beberapa contoh, mereka umumnya tampaknya terkait dengan pemrograman deklaratif, terutama seperti yang digunakan dalam ORM.
Dalam situasi ini, jika Anda menggunakan Metode # 2 Anda , dan subkelas mendefinisikan __new__
metode, itu akan dieksekusi setiap kali Anda menelepon SubClassOfSingleton()
- karena bertanggung jawab untuk memanggil metode yang mengembalikan instance yang disimpan. Dengan metaclass, itu hanya akan dipanggil sekali , ketika satu-satunya instance dibuat. Anda ingin menyesuaikan apa artinya memanggil kelas , yang ditentukan oleh jenisnya.
Secara umum, masuk akal untuk menggunakan metaclass untuk mengimplementasikan singleton. Singleton adalah spesial karena dibuat hanya sekali , dan metaclass adalah cara Anda menyesuaikan penciptaan kelas . Menggunakan metaclass memberi Anda lebih banyak kontrol jika Anda perlu menyesuaikan definisi kelas singleton dengan cara lain.
Singletons Anda tidak memerlukan pewarisan berganda (karena metaclass bukan kelas dasar), tetapi untuk subclass dari kelas yang dibuat yang menggunakan pewarisan berganda, Anda perlu memastikan bahwa kelas singleton adalah yang pertama / paling kiri dengan metaclass yang mendefinisikan ulang __call__
Ini sangat tidak mungkin menjadi masalah. Dict instance tidak ada dalam namespace instance sehingga tidak akan sengaja menimpanya.
Anda juga akan mendengar bahwa pola tunggal melanggar "Prinsip Tanggung Jawab Tunggal" - setiap kelas hanya boleh melakukan satu hal . Dengan begitu Anda tidak perlu khawatir mengacaukan satu hal yang dilakukan kode jika Anda perlu mengubah yang lain, karena mereka terpisah dan dienkapsulasi. Implementasi metaclass melewati tes ini . Metaclass bertanggung jawab untuk menegakkan pola dan kelas dan subclass yang dibuat tidak perlu menyadari bahwa mereka adalah lajang . Metode # 1 gagal tes ini, seperti yang Anda catat dengan "MyClass itu sendiri adalah fungsi, bukan kelas, jadi Anda tidak bisa memanggil metode kelas dari itu."
Versi Kompatibel Python 2 dan 3
Menulis sesuatu yang berfungsi baik di Python2 dan 3 membutuhkan menggunakan skema yang sedikit lebih rumit. Sejak metaclasses biasanya subclass dari jenis type
, itu mungkin untuk menggunakan satu untuk secara dinamis membuat kelas dasar perantara pada waktu berjalan dengan sebagai metaclass dan kemudian menggunakan bahwa sebagai baseclass dari masyarakat Singleton
kelas dasar. Lebih sulit untuk dijelaskan daripada melakukannya, seperti digambarkan selanjutnya:
# works in Python 2 & 3
class _Singleton(type):
""" A metaclass that creates a Singleton base class when called. """
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Singleton(_Singleton('SingletonMeta', (object,), {})): pass
class Logger(Singleton):
pass
Aspek ironis dari pendekatan ini adalah menggunakan subclass untuk mengimplementasikan metaclass. Satu keuntungan yang mungkin adalah bahwa, tidak seperti metaclass murni, isinstance(inst, Singleton)
akan kembali True
.
Koreksi
Pada topik lain, Anda mungkin sudah memperhatikan ini, tetapi implementasi kelas dasar dalam posting asli Anda salah. _instances
perlu direferensikan di kelas , Anda perlu menggunakan super()
atau Anda berulang , dan __new__
sebenarnya merupakan metode statis yang Anda harus lulus kelas , bukan metode kelas, karena kelas yang sebenarnya belum dibuat saat itu disebut. Semua hal ini akan berlaku juga untuk implementasi metaclass.
class Singleton(object):
_instances = {}
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
class MyClass(Singleton):
pass
c = MyClass()
Penghias Mengembalikan Kelas A
Saya awalnya menulis komentar tetapi terlalu lama, jadi saya akan menambahkan ini di sini. Metode # 4 lebih baik daripada versi dekorator lainnya, tetapi lebih banyak kode daripada yang dibutuhkan untuk seorang singleton, dan tidak jelas apa fungsinya.
Masalah utama berasal dari kelas adalah kelas dasar itu sendiri. Pertama, bukankah aneh memiliki kelas menjadi subkelas dari kelas yang hampir identik dengan nama yang sama yang hanya ada di __class__
atributnya? Ini juga berarti bahwa Anda tidak dapat menentukan metode yang memanggil metode dengan nama yang sama di kelas dasar mereka dengan super()
karena mereka akan recurse. Ini berarti kelas Anda tidak dapat dikustomisasi __new__
, dan tidak dapat berasal dari kelas apa pun yang perlu __init__
dipanggil.
Kapan harus menggunakan pola singleton
Case use Anda adalah salah satu contoh yang lebih baik dari keinginan untuk menggunakan singleton. Anda mengatakan dalam salah satu komentar, "Bagi saya penebangan selalu tampak sebagai calon alami untuk Singletons." Anda benar sekali .
Ketika orang mengatakan lajang itu buruk, alasan paling umum adalah mereka adalah negara bagian implisit . Sementara dengan variabel global dan impor modul tingkat atas adalah keadaan bersama eksplisit , objek lain yang diedarkan pada umumnya dipakai. Ini adalah poin yang bagus, dengan dua pengecualian .
Yang pertama, dan yang disebutkan di berbagai tempat, adalah ketika lajang konstan . Penggunaan konstanta global, terutama enum, diterima secara luas, dan dianggap waras karena bagaimanapun juga, tidak ada pengguna yang dapat mengacaukannya untuk pengguna lain mana pun . Ini sama benarnya dengan singleton yang konstan.
Pengecualian kedua, yang disebutkan lebih sedikit, adalah kebalikannya - ketika singleton hanya merupakan data sink , bukan sumber data (langsung atau tidak langsung). Inilah sebabnya mengapa penebang merasa seperti penggunaan "alami" untuk lajang. Karena berbagai pengguna tidak mengubah penebang dengan cara yang dipedulikan oleh pengguna lain, sebenarnya tidak ada status bersama . Ini meniadakan argumen utama terhadap pola tunggal, dan menjadikannya pilihan yang masuk akal karena mudah digunakan untuk tugas tersebut.
Berikut ini kutipan dari http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
Sekarang, ada satu jenis Singleton yang OK. Itu adalah singleton di mana semua objek yang dapat dijangkau tidak berubah. Jika semua objek tidak berubah daripada Singleton tidak memiliki keadaan global, karena semuanya konstan. Tetapi sangat mudah untuk mengubah jenis singleton ini menjadi yang bisa berubah, ini adalah lereng yang sangat licin. Karena itu, saya juga menentang para lajang ini, bukan karena mereka jahat, tetapi karena sangat mudah bagi mereka untuk menjadi jahat. (Sebagai catatan, enumerasi Java hanyalah lajang semacam ini. Selama Anda tidak memasukkan status ke enumerasi, Anda baik-baik saja, jadi tolong jangan.)
Jenis lajang lain, yang semi-dapat diterima adalah mereka yang tidak mempengaruhi eksekusi kode Anda, Mereka tidak memiliki "efek samping". Pencatatan adalah contoh sempurna. Itu dimuat dengan lajang dan negara global. Itu dapat diterima (karena itu tidak akan menyakiti Anda) karena aplikasi Anda tidak berlaku berbeda apakah logger yang diberikan diaktifkan atau tidak. Informasi di sini mengalir satu arah: Dari aplikasi Anda ke dalam logger. Bahkan penebang berpikir adalah keadaan global karena tidak ada informasi mengalir dari penebang ke aplikasi Anda, penebang dapat diterima. Anda harus tetap menyuntikkan logger Anda jika Anda ingin tes Anda untuk menegaskan bahwa ada sesuatu yang sedang login, tetapi secara umum Logger tidak berbahaya meskipun penuh dengan keadaan.
foo.x
atau jika Anda bersikerasFoo.x
bukanFoo().x
); gunakan atribut kelas dan metode statis / kelas (Foo.x
).