Metasınıf Kullan
Yöntem # 2'yi öneririm , ancak bir temel sınıftan daha çok bir metasınıf kullanmaktan daha iyisiniz . İşte örnek bir uygulama:
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
Veya Python3'te
class Logger(metaclass=Singleton):
pass
__init__
Sınıf her çağrıldığında çalıştırmak istiyorsanız ,
else:
cls._instances[cls].__init__(*args, **kwargs)
için if
de açıklamada Singleton.__call__
.
Metasınıflar hakkında birkaç söz. Metasınıf bir sınıfın sınıfıdır ; yani, sınıf meta sınıfının bir örneğidir . Python'da bir nesnenin metasınıfını ile bulabilirsiniz type(obj)
. Normal yeni stil sınıfları türdedir type
. Logger
yukarıdaki kodda class 'your_module.Singleton'
, tıpkı (yalnızca) örneğinin Logger
tipte olacağı gibi class 'your_module.Logger'
. Birlikte logger aradığınızda Logger()
, Python ilk metaclass sorar Logger
, Singleton
, örnek oluşturma önceden empted sağlayan ne yapacağını. Bu işlem, bir sınıfa __getattr__
özniteliklerinden birine başvurarak ne yapılacağını soran Python ile aynıdır myclass.attribute
.
Bir metasınıf esasen bir sınıf tanımının ne anlama geldiğine ve bu tanımın nasıl uygulanacağına karar verir . Örneğin , bkz. Http://code.activestate.com/recipes/498149/ , temelde struct
metasınıflar kullanarak Python'daki C stilini yeniden oluşturur . İplik Metasınıflar için bazı (somut) kullanım durumları nelerdir? ayrıca bazı örnekler de sağlarlar, özellikle ORM'lerde kullanıldığı gibi, genellikle bildirimsel programlama ile ilişkili görünmektedirler.
Bu durumda, Yöntem # 2'nizi kullanırsanız ve bir alt sınıf bir __new__
yöntem tanımlarsa , her aradığınızda yürütülürSubClassOfSingleton()
- depolanan örneği döndüren yöntemi çağırmaktan sorumludur. Bir metasınıfla, tek örnek oluşturulduğunda yalnızca bir kez çağrılacaktır . Sınıfa göre karar vermenin ne anlama geldiğini özelleştirmek istersiniz .
Genel olarak, bir singleton uygulamak için bir metasınıf kullanmak mantıklıdır . Singleton özeldir, çünkü sadece bir kez oluşturulur ve metasınıf bir sınıfın oluşturulmasını özelleştirmenizdir . Bir metasınıf kullanmak , singleton sınıf tanımlarını başka şekillerde özelleştirmeniz gerektiğinde size daha fazla kontrol sağlar .
Teklitonlarınızın birden fazla kalıtıma ihtiyacı yoktur (metasınıf temel bir sınıf olmadığından), ancak birden fazla kalıtım kullanan oluşturulan sınıfın alt sınıfları için , singleton sınıfının, yeniden tanımlayan bir metasınıflı ilk / en soldaki sınıf olduğundan emin olmanız gerekir. __call__
Bunun bir sorun olması pek olası değildir. Örnek diktesi, örneğin ad alanında değildir, dolayısıyla yanlışlıkla üzerine yazmaz.
Ayrıca, tekil paternin "Tek Sorumluluk İlkesi" ni ihlal ettiğini de duyacaksınız - her sınıf sadece bir şey yapmalıdır . Bu şekilde, kodun bir başkasını değiştirmeniz gerekiyorsa bir şeyi karıştırmaktan endişelenmenize gerek kalmaz, çünkü bunlar ayrı ve kapsüllenmiştir. Metaclass uygulaması bu testi geçer . Metaclass, kalıbın uygulanmasından sorumludur ve yaratılan sınıf ve alt sınıfların tekton olduklarının farkında olmaları gerekmez . Yöntem 1. Birlikte belirtildiği gibi, bu testi başarısız "ondan sınıf yöntemleri çağırmak olamaz yani kendisi aa fonksiyon, bir sınıftır Sınıfım."
Python 2 ve 3 Uyumlu Sürüm
Hem Python2 hem de 3'te çalışan bir şey yazmak biraz daha karmaşık bir şema kullanmayı gerektirir. Metasınıflar genellikle tip alt sınıflar olduğundan , metasınıfı type
olarak çalışma zamanında dinamik olarak bir ara taban sınıfı oluşturmak için birini kullanmak ve daha sonra bunu kamu Singleton
taban sınıfının temel sınıfı olarak kullanmak mümkündür. Daha sonra açıklandığı gibi açıklamak daha zordur:
# 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
Bu yaklaşımın ironik bir yönü, bir metasınıf uygulamak için alt sınıflandırma kullanmasıdır. Olası bir avantaj, saf bir metasınıfın aksine isinstance(inst, Singleton)
geri dönmesidir True
.
Düzeltmeler
Başka bir konuda, muhtemelen bunu zaten fark ettiniz, ancak orijinal yayınınızdaki temel sınıf uygulaması yanlış. _instances
ihtiyaçlar edilecek sınıfına başvurulan , kullanmak gerekmez super()
veya sen recursing ve __new__
sen zorunda aslında statik bir yöntemdir için sınıf geçmek fiili sınıf olarak değil, bir sınıf yöntemi oluşturulmadı henüz ne zaman denir. Bütün bunlar bir metasınıf uygulaması için de geçerli olacak.
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()
Sınıf Dönen Dekoratör
Başlangıçta bir yorum yazıyordum ama çok uzundu, bu yüzden buraya ekleyeceğim. Yöntem # 4 , diğer dekoratör sürümünden daha iyidir, ancak tek birton için gerekenden daha fazla koddur ve ne yaptığı açık değildir.
Temel problemler sınıfın kendi temel sınıfı olmasından kaynaklanmaktadır. Birincisi, bir sınıfın, yalnızca __class__
özniteliğinde var olan aynı ada sahip neredeyse aynı sınıfın bir alt sınıfı olması garip değil mi? Bu aynı zamanda tanımlamak anlamına gelir onların temel sınıf aynı adı taşıyan yöntemini çağırın herhangi yöntemleri ile super()
onlar Recurse çünkü. Bu, sınıfınızın özelleştirilemeyeceği __new__
ve __init__
çağrılması gereken sınıflardan türeyemeyeceği anlamına gelir .
Singleton paterni ne zaman kullanılır?
Kullanım durumunuz, singleton kullanmak isteyebileceğiniz en iyi örneklerden biridir. Bir yorumda "Bana göre giriş her zaman Singletons için doğal bir aday gibi göründü" diyorsunuz. Sen kesinlikle doğru .
İnsanlar singletonların kötü olduğunu söylediklerinde en yaygın neden örtülü paylaşılan devlet olmalarıdır . Genel değişkenler ve üst düzey modül içe aktarmaları açık paylaşılan durum olsa da, iletilen diğer nesneler genellikle somutlaştırılır. Bu iki istisna dışında iyi bir nokta .
Birincisi ve çeşitli yerlerde bahsedilen, tektonların sabit olduğu zamandır . Küresel sabitler, özellikle çeteleler kullanımı yaygın olarak kabul ve aklı başında sayılır, çünkü ne olursa olsun, başka bir kullanıcı için kullanıcı hiçbiri mahvedebilir onları . Bu, sabit bir singleton için de aynı şekilde geçerlidir.
Daha az bahsedilen ikinci istisna tam tersidir - singleton bir veri kaynağı değil, doğrudan veya dolaylı olarak sadece bir veri havuzu olduğunda. Bu nedenle logger'lar singletonlar için "doğal" bir kullanım gibi hissederler. Çeşitli kullanıcılar , günlükçüleri diğer kullanıcıların önem verdiği şekilde değiştirmediği için , gerçekten paylaşılan bir durum yoktur . Bu, singleton desenine karşı birincil argümanı reddeder ve görev için kullanım kolaylığı nedeniyle onları makul bir seçim yapar .
Http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html adresinden bir alıntı :
Şimdi, bir tür Singleton var. Bu, ulaşılabilir tüm nesnelerin değişmez olduğu bir singleton. Tüm nesneler Singleton'dan değişmezse, her şey sabit olduğu için küresel bir durumu yoktur. Ancak bu tür singletonu mutable olana dönüştürmek çok kolaydır, çok kaygan bir eğimdir. Bu nedenle, bu Singletonlara da karşıyım, kötü oldukları için değil, kötü gitmeleri çok kolay olduğu için. (Bir yan not olarak Java numaralandırması sadece bu tür tekil tonlardır. Numaralandırmanıza durum koymadığınız sürece sorun değil, lütfen yapmayın.)
Yarı-kabul edilebilir olan diğer Singletons, kodunuzun yürütülmesini etkilemeyenlerdir, "yan etkisi" yoktur. Günlüğe kaydetme mükemmel bir örnektir. Singletons ve global durum ile yüklenir. Uygulamanız belirli bir günlükçünün etkinleştirilip etkinleştirilmediği konusunda farklı davranmadığı için (içinde olduğu gibi size zarar vermeyecektir) kabul edilebilir. Buradaki bilgiler tek bir şekilde akar: Uygulamanızdan kaydediciye. Günlükçülerden uygulamanıza hiçbir bilgi akışı olmadığından, günlükçüler küresel durumdur, günlükçüler kabul edilebilir. Testinizin bir şeyin günlüğe kaydedildiğini iddia etmesini istiyorsanız, kaydedicinizi enjekte etmelisiniz, ancak genel olarak Logger'lar durum dolu olmasına rağmen zararlı değildir.
foo.x
veya eğer ısrarFoo.x
yerineFoo().x
); sınıf özniteliklerini ve statik / sınıf yöntemlerini (Foo.x
) kullanın.