ใช้ Metaclass
ฉันอยากจะแนะนำวิธีที่ # 2แต่คุณควรใช้metaclassดีกว่าคลาสพื้นฐาน นี่คือตัวอย่างการใช้งาน:
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
หรือใน Python3
class Logger(metaclass=Singleton):
pass
ถ้าคุณต้องการเรียกใช้__init__
ทุกครั้งที่เรียกคลาสเพิ่ม
else:
cls._instances[cls].__init__(*args, **kwargs)
กับคำสั่งในif
Singleton.__call__
คำสองสามคำเกี่ยวกับ metaclasses metaclass เป็นชั้นของชั้น ; นั่นคือชั้นเรียนเป็นตัวอย่างของ metaclass คุณจะพบ metaclass type(obj)
ของวัตถุในหลามกับ type
ปกติการเรียนแบบใหม่เป็นประเภท Logger
ในรหัสข้างต้นจะเป็นชนิดclass 'your_module.Singleton'
เช่นเดียวกับ (เท่านั้น) ตัวอย่างของการจะเป็นประเภทLogger
class 'your_module.Logger'
เมื่อคุณเรียกคนตัดไม้กับLogger()
งูใหญ่ครั้งแรกถาม metaclass ของLogger
, Singleton
สิ่งที่ต้องทำทำให้การสร้างอินสแตนซ์ที่จะจองไว้แล้ว กระบวนการนี้จะเป็นเช่นเดียวกับงูหลามถามชั้นว่าจะทำอย่างไรโทรเมื่อคุณอ้างอิงหนึ่งของคุณลักษณะของมันโดยการทำ__getattr__
myclass.attribute
เมตาคลาสจะเป็นตัวตัดสินความหมายของคลาสและวิธีการใช้นิยามนั้น ดูตัวอย่างhttp://code.activestate.com/recipes/498149/ซึ่งสร้าง C-style struct
ใน Python โดยใช้ metaclasses เป็นหลัก เธรดอะไรบ้าง (คอนกรีต) ใช้กรณีสำหรับ metaclasses? ยังมีตัวอย่างบางอย่างโดยทั่วไปดูเหมือนว่าจะเกี่ยวข้องกับการเขียนโปรแกรมเชิงประกาศโดยเฉพาะอย่างยิ่งที่ใช้ใน ORM
ในสถานการณ์เช่นนี้หากคุณใช้วิธีที่ # 2และคลาสย่อยกำหนด__new__
วิธีการนั้นจะถูกดำเนินการทุกครั้งที่คุณโทรSubClassOfSingleton()
- เพราะมีหน้าที่รับผิดชอบในการเรียกวิธีการที่ส่งกลับอินสแตนซ์ที่เก็บไว้ ด้วย metaclass มันจะถูกเรียกเพียงครั้งเดียวเมื่อมีการสร้างอินสแตนซ์เท่านั้น คุณต้องการปรับแต่งความหมายในการเรียกชั้นเรียนซึ่งกำหนดโดยประเภทของมัน
โดยทั่วไปแล้วมันทำให้ความรู้สึกที่จะใช้ metaclass ที่จะใช้เดี่ยว เดี่ยวเป็นพิเศษเพราะถูกสร้างขึ้นเพียงครั้งเดียวและ metaclass เป็นวิธีที่คุณปรับแต่งการสร้างชั้นเรียน การใช้ metaclass ช่วยให้คุณควบคุมได้มากขึ้นในกรณีที่คุณต้องการกำหนดคำจำกัดความของชั้นเดี่ยวในรูปแบบอื่น ๆ
ซิงเกิลตันของคุณไม่จำเป็นต้องมีการสืบทอดหลายอัน (เนื่องจาก metaclass ไม่ใช่คลาสพื้นฐาน) แต่สำหรับคลาสย่อยของคลาสที่สร้างขึ้นซึ่งใช้การสืบทอดหลายคลาสคุณจำเป็นต้องตรวจสอบให้แน่ใจว่าคลาส singleton เป็นคลาสแรก / ซ้ายสุดที่มี metaclass ที่กำหนดใหม่__call__
สิ่งนี้ไม่น่าจะเป็นปัญหา อินสแตนซ์ของ dict ไม่อยู่ในเนมสเปซของอินสแตนซ์ดังนั้นจะไม่เขียนทับมันโดยไม่ตั้งใจ
นอกจากนี้คุณยังจะได้ทราบว่ารูปแบบเดี่ยวละเมิด "Single รับผิดชอบหลักการ" - แต่ละชั้นควรทำเพียงสิ่งเดียว ด้วยวิธีนี้คุณไม่ต้องกังวลกับการเลอะสิ่งใดสิ่งหนึ่งรหัสทำถ้าคุณต้องการเปลี่ยนอีกเพราะพวกเขาแยกและห่อหุ้ม การใช้งาน metaclass ผ่านการทดสอบนี้ metaclass เป็นผู้รับผิดชอบในการบังคับใช้รูปแบบและชั้นที่สร้างขึ้นและ subclasses ไม่จำเป็นต้องตระหนักว่าพวกเขาจะ singletons วิธี # 1ล้มเหลวในการทดสอบนี้ตามที่คุณบันทึกไว้ด้วย "MyClass เองเป็นฟังก์ชัน aa ไม่ใช่คลาสดังนั้นคุณจึงไม่สามารถเรียกเมธอดคลาสได้
Python 2 และ 3 รองรับเวอร์ชั่น
การเขียนสิ่งที่ใช้ได้ทั้ง Python2 และ 3 จำเป็นต้องใช้โครงร่างที่ซับซ้อนกว่าเล็กน้อย ตั้งแต่ metaclasses มักจะ subclasses ชนิดtype
ก็เป็นไปได้ที่จะใช้อย่างใดอย่างหนึ่งแบบไดนามิกสร้างชั้นฐานตัวกลางในเวลาทำงานกับมันเป็น metaclass และจากนั้นใช้ที่เป็น BaseClass ของประชาชนSingleton
ชั้นฐาน เป็นการยากที่จะอธิบายมากกว่าที่จะทำดังที่แสดงไว้ถัดไป:
# 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
ด้านประชดของวิธีนี้คือมันใช้ subclassing เพื่อใช้ metaclass ข้อดีอย่างหนึ่งที่เป็นไปได้ว่าแตกต่างกับ metaclass บริสุทธิ์จะกลับมาisinstance(inst, Singleton)
True
ราชทัณฑ์
ในหัวข้ออื่นคุณอาจสังเกตเห็นแล้ว แต่การใช้งานคลาสพื้นฐานในโพสต์ต้นฉบับของคุณผิด _instances
จำเป็นต้องมีการอ้างอิงในชั้นเรียนคุณจำเป็นต้องใช้super()
หรือคุณกำลังเรียกซ้ำและ__new__
จริงๆแล้วเป็นวิธีแบบคงที่คุณต้องผ่านชั้นเรียนไปยังไม่ใช่วิธีชั้นเรียนเนื่องจากชั้นจริงยังไม่ได้สร้างขึ้นเมื่อมัน ถูกเรียก. สิ่งเหล่านี้ทั้งหมดจะเป็นจริงสำหรับการใช้งาน 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()
มัณฑนากรคืน Class A
ตอนแรกฉันเขียนความคิดเห็น แต่มันยาวเกินไปฉันจะเพิ่มที่นี่ วิธีที่ # 4ดีกว่ารุ่นมัณฑนากรอื่น ๆ แต่มันเป็นโค้ดมากกว่าที่จำเป็นสำหรับซิงเกิลตันและไม่ชัดเจนว่ามันทำอะไร
ปัญหาหลักเกิดจากคลาสเนื่องจากเป็นคลาสพื้นฐานของตัวเอง อันดับแรกมันไม่แปลกที่การมีคลาสเป็นคลาสย่อยของคลาสที่เกือบเหมือนกันโดยมีชื่อเดียวกันที่มีอยู่ใน__class__
แอตทริบิวต์เท่านั้น นอกจากนี้ยังหมายความว่าคุณไม่สามารถกำหนดวิธีการใด ๆ ที่เรียกใช้วิธีการที่มีชื่อเดียวกันในคลาสพื้นฐานของพวกเขาด้วยsuper()
เพราะพวกเขาจะเรียกคืน นี่หมายความว่าคลาสของคุณไม่สามารถปรับแต่งได้__new__
และไม่สามารถสืบทอดมาจากคลาสใด ๆ ที่จำเป็นต้อง__init__
เรียกใช้
ควรใช้รูปแบบซิงเกิลเมื่อใด
กรณีการใช้งานของคุณเป็นหนึ่งในตัวอย่างที่ดีกว่าของความต้องการใช้ซิงเกิล คุณพูดในความคิดเห็นอย่างใดอย่างหนึ่งว่า "สำหรับฉันการบันทึกเป็นเรื่องปกติสำหรับ Singletons" คุณอย่างถูกต้อง
เมื่อคนบอกว่า singletons ไม่ดีเหตุผลที่พบมากที่สุดคือพวกเขามีนัยรัฐที่ใช้ร่วมกัน ในขณะที่มีตัวแปรทั่วโลกและการนำเข้าโมดูลระดับบนสุดเป็นสถานะที่ใช้ร่วมกันอย่างชัดเจนวัตถุอื่น ๆ ที่ผ่านไปโดยทั่วไปจะถูกยกตัวอย่าง นี้เป็นจุดที่ดีที่มีสองข้อยกเว้น
คนแรกและคนที่ได้รับการกล่าวถึงในสถานที่ต่างๆคือเมื่อ singletons ที่มีอย่างต่อเนื่อง การใช้ค่าคงที่ทั่วโลกโดยเฉพาะอย่างยิ่ง enums เป็นที่ยอมรับกันอย่างแพร่หลายและคิดว่ามีเหตุผลเพราะไม่ว่าสิ่งที่ไม่มีผู้ใช้สามารถรับประทานอาหารพวกเขาขึ้นสำหรับผู้ใช้อื่น ๆ นี่คือความจริงที่เท่าเทียมกันสำหรับซิงเกิลคงที่
ข้อยกเว้นที่สองซึ่งพูดถึงน้อยกว่านั้นคือตรงกันข้าม - เมื่อ singleton เป็นเพียง sink ข้อมูลไม่ใช่แหล่งข้อมูล (โดยตรงหรือโดยอ้อม) นี่คือเหตุผลที่คนตัดไม้รู้สึกเหมือนใช้ "ธรรมชาติ" สำหรับซิงเกิลตัน ในฐานะที่เป็นผู้ใช้ต่างๆจะไม่มีการเปลี่ยนแปลงตัดไม้ในรูปแบบที่ผู้ใช้อื่น ๆ จะดูแลเกี่ยวกับการมีรัฐที่ใช้ร่วมกันไม่ได้จริงๆ สิ่งนี้ขัดแย้งกับการโต้แย้งหลักกับรูปแบบซิงเกิลและทำให้พวกมันเป็นตัวเลือกที่สมเหตุสมผลเพราะความสะดวกในการใช้งาน
นี่คือคำพูดจากhttp://googletesting.blogspot.com/2008/08/root-cause-cause-of-singletons.html :
ตอนนี้มีซิงเกิลตันหนึ่งเดียวซึ่งก็โอเค นั่นคือซิงเกิลที่วัตถุที่เข้าถึงได้ทั้งหมดไม่เปลี่ยนรูป หากวัตถุทั้งหมดไม่เปลี่ยนรูปแบบจากซิงเกิลตันไม่มีสถานะโกลบอลเพราะทุกอย่างคงที่ แต่มันง่ายมากที่จะเปลี่ยนชนิดของซิงเกิลตันให้กลายเป็นอันที่ไม่แน่นอนมันเป็นความชันที่ลื่นมาก ดังนั้นฉันก็เลยต่อต้านซิงเกิลเหล่านี้ด้วยไม่ใช่เพราะพวกเขาไม่ดี แต่เพราะมันง่ายสำหรับพวกเขาที่จะแย่ (ในฐานะที่เป็นหมายเหตุด้าน Java การแจงนับเป็นประเภทเดี่ยวเหล่านี้ตราบใดที่คุณไม่ใส่สถานะในการแจงนับของคุณคุณก็โอเคดังนั้นโปรดอย่าทำ)
Singletons ชนิดอื่นซึ่งเป็นแบบกึ่งยอมรับได้คือแบบที่ไม่มีผลต่อการเรียกใช้โค้ดของคุณพวกเขาไม่มี "ผลข้างเคียง" การบันทึกเป็นตัวอย่างที่สมบูรณ์แบบ มันเต็มไปด้วย Singletons และ global state เป็นที่ยอมรับได้ (เพราะจะไม่ทำให้คุณเจ็บ) เนื่องจากแอปพลิเคชันของคุณไม่ทำงานแตกต่างกันไม่ว่าจะเปิดใช้งานตัวบันทึกที่กำหนดหรือไม่ก็ตาม ข้อมูลในที่นี้จะไหลทางเดียว: จากใบสมัครของคุณไปยังตัวบันทึก แม้ว่าผู้บันทึกคิดว่าเป็นสถานะทั่วโลกเนื่องจากไม่มีข้อมูลไหลจากตัวบันทึกลงในแอปพลิเคชันของคุณตัวบันทึกสามารถยอมรับได้ คุณควรฉีด logger ของคุณถ้าคุณต้องการให้การทดสอบยืนยันว่ามีบางสิ่งที่ถูกบันทึก แต่โดยทั่วไปแล้ว Loggers จะไม่เป็นอันตรายแม้จะเต็มไปด้วยสถานะ
foo.x
หรือถ้าคุณยืนยันFoo.x
แทนFoo().x
); ใช้แอตทริบิวต์ class และ static / class methods (Foo.x
)