สร้างซิงเกิลตันใน Python


945

คำถามนี้ไม่ได้มีไว้สำหรับการอภิปรายว่ารูปแบบการออกแบบซิงเกิลตันนั้นน่าพึงพอใจหรือไม่นั้นเป็นรูปแบบการต่อต้านหรือสงครามทางศาสนาใด ๆ แต่เพื่อพูดคุยว่ารูปแบบการใช้งานที่ดีที่สุดในไพ ธ อนนั้นเป็นไปได้อย่างไร ในกรณีนี้ฉันกำหนด 'pythonic ส่วนใหญ่จะหมายถึงว่ามันเป็นไปตามหลักการน้อยมหัศจรรย์'

ฉันมีหลายชั้นเรียนซึ่งจะกลายเป็น singletons (กรณีใช้งานของฉันสำหรับคนตัดไม้ แต่นี่ไม่ใช่สิ่งสำคัญ) ฉันไม่ต้องการที่จะถ่วงเวลาเรียนหลายครั้งพร้อมกับเพิ่มชัยชนะเมื่อฉันสามารถสืบทอดหรือตกแต่ง

วิธีการที่ดีที่สุด:


วิธีที่ 1: มัณฑนากร

def singleton(class_):
    instances = {}
    def getinstance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return getinstance

@singleton
class MyClass(BaseClass):
    pass

ข้อดี

  • มัณฑนากรเป็นสารเติมแต่งในลักษณะที่มักใช้งานง่ายกว่าการสืบทอดหลายแบบ

จุดด้อย

  • ในขณะที่วัตถุที่สร้างขึ้นโดยใช้ MyClass () จะเป็นวัตถุซิงเกิลจริง MyClass เองนั้นเป็นฟังก์ชั่น aa ไม่ใช่คลาสดังนั้นคุณจึงไม่สามารถเรียกเมธอดคลาสได้ นอกจากนี้สำหรับm = MyClass(); n = MyClass(); o = type(n)();ตอนนั้นm == n && m != o && n != o

วิธีที่ 2: คลาสพื้นฐาน

class Singleton(object):
    _instance = None
    def __new__(class_, *args, **kwargs):
        if not isinstance(class_._instance, class_):
            class_._instance = object.__new__(class_, *args, **kwargs)
        return class_._instance

class MyClass(Singleton, BaseClass):
    pass

ข้อดี

  • มันเป็นคลาสที่แท้จริง

จุดด้อย

  • มรดกที่หลากหลาย - เอ๊ะ! __new__สามารถเขียนทับในระหว่างการสืบทอดจากชั้นฐานที่สอง? เราต้องคิดมากกว่าที่จำเป็น

วิธีที่ 3: 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]

#Python2
class MyClass(BaseClass):
    __metaclass__ = Singleton

#Python3
class MyClass(BaseClass, metaclass=Singleton):
    pass

ข้อดี

  • มันเป็นคลาสที่แท้จริง
  • อัตโนมัติครอบคลุมมรดกอย่างน่าอัศจรรย์
  • ใช้__metaclass__เพื่อจุดประสงค์ที่เหมาะสม (และทำให้ฉันตระหนักถึง)

จุดด้อย

  • ยังมี .... บ้าง?

วิธีที่ 4: มัณฑนากรส่งคืนคลาสที่มีชื่อเดียวกัน

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class_, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w,
                                    class_).__new__(class_,
                                                    *args,
                                                    **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(BaseClass):
    pass

ข้อดี

  • มันเป็นคลาสที่แท้จริง
  • อัตโนมัติครอบคลุมมรดกอย่างน่าอัศจรรย์

จุดด้อย

  • ไม่มีค่าใช้จ่ายสำหรับการสร้างคลาสใหม่แต่ละรายการหรือไม่ ที่นี่เรากำลังสร้างสองคลาสสำหรับแต่ละชั้นเรียนที่เราต้องการสร้างซิงเกิลตัน ในขณะนี้ใช้ได้ในกรณีของฉันฉันกังวลว่านี่อาจไม่ขยาย แน่นอนว่ามีเรื่องที่ถกเถียงกันอยู่ว่ามันง่ายเกินไปที่จะปรับขนาดรูปแบบนี้หรือไม่ ...
  • ประเด็นของ_sealedคุณลักษณะคืออะไร
  • ไม่สามารถเรียกวิธีการที่มีชื่อเดียวกันกับคลาสพื้นฐานที่ใช้super()เพราะจะเรียกคืน ซึ่งหมายความว่าคุณไม่สามารถปรับแต่งและไม่สามารถซับคลาสคลาสที่ตอบสนองความต้องการให้คุณโทรได้ถึง__new____init__

วิธีที่ 5: โมดูล

ไฟล์โมดูล singleton.py

ข้อดี

  • เรียบง่ายดีกว่าซับซ้อน

จุดด้อย


12
อีกสามเทคนิค: ใช้โมดูลแทน (มัก - โดยทั่วไปแล้วฉันคิดว่า - นี่เป็นรูปแบบที่เหมาะสมกว่าสำหรับ Python แต่ขึ้นอยู่กับสิ่งที่คุณกำลังทำอยู่) ทำอินสแตนซ์เดียวและจัดการกับมันแทน ( foo.xหรือถ้าคุณยืนยันFoo.xแทนFoo().x); ใช้แอตทริบิวต์ class และ static / class methods ( Foo.x)
Chris Morgan

10
@ChrisMorgan: ถ้าคุณจะใช้วิธีการเรียนแบบสแตติก / แบบคงที่เท่านั้นไม่ต้องกังวลกับการเรียน
Cat Plus Plus

2
@Cat: ผลคล้ายกัน แต่เหตุผลที่อยู่เบื้องหลังการสร้างตัวแปรทั่วโลกสามารถเป็นอะไรก็ได้รวมถึงไม่รู้อะไรเลยดีกว่า ทำไมคนเราสร้างซิงเกิลตัน? หากคุณต้องถามว่าคุณไม่ควรอยู่ที่นี่ explicitness นี้ไม่เพียง pythonic เพิ่มเติม แต่ทำให้การบำรุงรักษาง่ายขึ้นมาก ใช่แล้วซิงเกิลตันนั้นเป็นน้ำตาลซินแทคติคสำหรับกัลปัง แต่จากนั้นชั้นเรียนก็เป็นน้ำตาลซินแทคติกสำหรับสิ่งที่ไม่น่าดูและฉันไม่คิดว่าจะมีใครบอกคุณได้ว่าคุณดีกว่าถ้าไม่มีพวกเขา
theheadofabroom

14
@BiggAl: Singletons ไม่ใช่ Pythonic ไม่ว่าคุณจะใช้มันอย่างไร พวกเขาเป็นสัญลักษณ์ของการออกแบบที่สมบูรณ์แบบที่สุด
Cat Plus Plus

8
ความเชื่อมั่นต่อต้าน signletons คือการเขียนโปรแกรมลัทธิสินค้าที่เลวร้ายที่สุด เช่นเดียวกันกับคนที่ได้ยิน (ไม่กี่คนที่อ่านจริง) "คำสั่งไปถือว่าเป็นอันตราย" และคิดว่า gotos เป็นสัญญาณของรหัสที่ไม่ดีโดยไม่คำนึงถึงบริบท
Hejazzman

คำตอบ:


658

ใช้ 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)

กับคำสั่งในifSingleton.__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 จะไม่เป็นอันตรายแม้จะเต็มไปด้วยสถานะ


5
ไม่ซิงเกิลไม่เคยดีเลย การบันทึกอาจเป็นตัวเลือกที่ดีในการเป็นระดับโลก (แย่มากอย่างที่เคยเป็น) แต่ไม่แน่นอนว่าเป็นซิงเกิล
Cat Plus Plus

11
ดูที่googletesting.blogspot.com/2008/08/... โดยทั่วไปแล้วจะเป็น anti-singleton (สำหรับเหตุผลที่ดี) แต่มีการอธิบายที่ดีว่าทำไม singletons และ singletons ที่ไม่มีผลข้างเคียงไม่มีปัญหาเดียวกันหากคุณระมัดระวัง ฉันจะพูดถึงมันสักหน่อยในตอนท้ายของโพสต์
agf

4
ปัญหาของฉันกับ singletons คือนนิ่งโง่ของ "เพียงหนึ่งอินสแตนซ์" นั่นและตันของปัญหาความปลอดภัยด้าย และซ่อนการพึ่งพา Globals ไม่ดีและ singletons เป็นเพียง globals ที่มีปัญหามากขึ้น
Cat Plus Plus

4
@Cat มีการใช้งานที่ดีมากสำหรับ singletons Lazy instantiation ของโมดูลฮาร์ดแวร์ (โดยเฉพาะอย่างยิ่งในแอปพลิเคชั่นเธรดเดี่ยว) เป็นหนึ่งในนั้น
พอล Manta

3
@Alcott __new__ใน metaclass คือเมื่อคลาสใหม่ - เมื่อถูกกำหนดไม่ใช่เมื่ออินสแตนซ์นั้นเป็นใหม่ การเรียกคลาส ( MyClass()) เป็นการดำเนินการที่คุณต้องการแทนที่ไม่ใช่คำจำกัดความของคลาส ถ้าคุณอยากจะเข้าใจวิธีการทำงานของงูหลามสิ่งที่ดีที่สุดที่คุณสามารถทำได้ (นอกเหนือจากให้ใช้มัน) เป็นอ่านdocs.python.org/reference/datamodel.html การอ้างอิงที่ดีใน metaclasses เป็นeli.thegreenplace.net/2011/08/14/python-metaclasses-by-example บทความที่ดีเกี่ยวกับ singletons คือซีรีส์จากบล็อก google ที่ฉันเชื่อมโยงในคำตอบนี้
agf

91
class Foo(object):
     pass

some_global_variable = Foo()

โมดูลจะถูกนำเข้าเพียงครั้งเดียวทุกอย่างอื่นจะคิด อย่าใช้ซิงเกิลตันและพยายามอย่าใช้กลม


14
ทำไมคุณถึงพูดว่า "อย่าใช้ซิงเกิลตัน" ด้วยเหตุผลใด
Alcott

3
สิ่งนี้จะไม่สามารถใช้งานได้หากต้องเลือกดองเดี่ยว ใช้ตัวอย่างที่คุณให้ไว้:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
dividebyzero

4
@dividebyzero: isผู้ดำเนินการทดสอบความเท่าเทียมกันของตัวชี้ ฉันจะแปลกใจทีเดียว --- ถึงจุดที่เรียกว่าเป็นบั๊ก - ถ้าpickle.loadsส่งคืนการอ้างอิงไปยังวัตถุที่มีอยู่แล้วแทนที่จะอ้างอิงไปยังวัตถุที่สร้างขึ้นใหม่ ดังนั้นการทดสอบs is s1ว่าไม่ได้บอกอะไรคุณเกี่ยวกับความเหมาะสมของการใช้โมดูลเป็นซิงเกิลตัน
Jonas Kölker

1
@ JonasKölker pickle.loads()ไม่ทำอย่างนั้นอยู่แล้วเช่นสำหรับกรณีของและbool ผลตอบแทนNoneTypepickle.loads(pickle.dumps(False)) is FalseTrue
Dan Passaro

2
@ leo-the-manic: จุดยุติธรรม; แต่นั่นเป็นเพียงผลข้างเคียงของงูหลามฝึกงานวัตถุTrue, FalseและNone, pickle.loadsและมีอะไรจะทำอย่างไรกับการที่อยู่เบื้องหลังรหัส นอกจากนี้ยังปลอดภัยที่จะทำเฉพาะกับวัตถุแบบอ่านอย่างเดียว ถ้าหากpickle.loadsต้องการส่งคืนการอ้างอิงไปยังวัตถุที่แก้ไขได้ที่มีอยู่แล้วเช่นโมดูลซึ่งจะเป็นข้อบกพร่อง (และดังนั้นฉันกำลังยืนอยู่โดยนัยของฉันว่าตัวอย่างรหัสของ dividebyzero ไม่ได้พิสูจน์อะไรเลย)
Jonas Kölker

69

ใช้โมดูล มันถูกนำเข้าเพียงครั้งเดียว กำหนดตัวแปรทั่วโลกบางอย่างในนั้น - พวกเขาจะเป็น 'แอตทริบิวต์' ของซิงเกิล เพิ่มฟังก์ชั่นบางอย่าง - 'วิธีการ' ของซิงเกิล


11
ดังนั้นสิ่งที่คุณจะได้คือ ... ไม่ได้เรียน คุณไม่สามารถใช้เป็นคลาสได้คุณไม่สามารถใช้คลาสอื่นร่วมกับคลาสนั้นคุณใช้ไวยากรณ์การนำเข้าและในทันทีที่คุณสูญเสียผลประโยชน์ทั้งหมดของ OOP ...
theheadofabroom

16
ถ้าคุณสามารถยึดคลาสอื่น ๆ กับคลาสนั้นมันอาจจะไม่ใช่ซิงเกิล คุณสามารถสร้างหนึ่งในคลาสที่ได้รับ แต่ยังเป็นหนึ่งในคลาสพื้นฐาน แต่คลาสที่ได้รับนั้นยังเป็นสมาชิกของฐานและคุณมีสองฐานซึ่งหนึ่งที่คุณควรจะใช้?
SingleNegationElimination

สิ่งนี้ใช้ไม่ได้กับโมดูลต่างๆ ในโมดูล "main" ของฉันฉันตั้งค่า ฉันอ้างอิงมันในโมดูลอื่นและโมฆะ ถอนหายใจ
Paul Kenjora

1
@PaulKenjora คุณต้องมีข้อผิดพลาดในรหัสของคุณ หากคุณกำหนดตัวแปรโกลบอลในโมดูลเมื่อคุณเข้าถึงจากโมดูลอื่นมันควรมีค่า
warvariuc

มันมีค่า แต่เมื่อฉันเปลี่ยนมันจะไม่ถูกเก็บรักษาไว้ ฉันลงเอยด้วยการใช้คลาสที่มีคุณสมบัติในโมดูลที่ใช้งานได้ ประเภทธรรมดาเมื่อ globals ไม่ได้ผลสำหรับฉัน (พวกเขาสูญเสียคุณค่าทันทีที่มีการเปลี่ยนแปลงขอบเขต)
Paul Kenjora

29

คุณอาจไม่จำเป็นต้องใช้ซิงเกิลตันใน Python เพียงแค่กำหนดข้อมูลและฟังก์ชั่นทั้งหมดของคุณในโมดูลและคุณมีเดือยเดี่ยว

ถ้าคุณต้องมีชั้นเรียนเดี่ยวจริงๆแล้วฉันจะไปกับ:

class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

ใช้:

from mysingleton import my_singleton
my_singleton.foo()

โดยที่ mysingleton.py เป็นชื่อไฟล์ของคุณที่ My_Singleton กำหนดไว้ใช้งานได้เพราะหลังจากนำเข้าไฟล์ครั้งแรก Python จะไม่เรียกใช้รหัสอีกครั้ง


4
ส่วนใหญ่จริง แต่บางครั้งก็ไม่เพียงพอ เช่นฉันมีโครงการที่ต้องการบันทึกอินสแตนซ์ของคลาสจำนวนมากที่ระดับ DEBUG ฉันต้องการตัวเลือกบรรทัดคำสั่งที่แยกวิเคราะห์เมื่อเริ่มต้นเพื่อกำหนดระดับการบันทึกที่ผู้ใช้ระบุก่อนที่คลาสเหล่านั้น อินสแตนซ์ระดับโมดูลทำให้เกิดปัญหาได้ เป็นไปได้ว่าฉันสามารถจัดโครงสร้างแอพอย่างระมัดระวังเพื่อไม่ให้คลาสเหล่านั้นทั้งหมดถูกนำเข้าจนกว่าการประมวลผล CLI จะเสร็จสิ้น แต่โครงสร้างตามธรรมชาติของแอพของฉันมีความสำคัญมากกว่าการยึดมั่นแบบดันทุรังเป็น ทำค่อนข้างเรียบร้อย
CryingCyclops

ถ้าคุณต้องทดสอบรหัสของคุณในขณะทำการแก้ไข my_singleton มันจะเป็นไปได้ไหม ตั้งแต่ my_singleton นี้สามารถยกตัวอย่างในโมดูลอื่น ๆ
Naveen

@Naveen - my_singleton เป็นวัตถุชิ้นเดียว หากคุณ "แก้ไข" การเปลี่ยนแปลงนั้นจะมีผลต่อการอ้างอิงในอนาคตทั้งหมดแม้แต่ในโมดูลอื่น ๆ
Alan Dyke

16

นี่คือหนึ่งซับสำหรับคุณ:

singleton = lambda c: c()

นี่คือวิธีที่คุณใช้:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

วัตถุของคุณได้รับการยกตัวอย่างอย่างกระตือรือร้น นี่อาจเป็นหรือไม่ใช่สิ่งที่คุณต้องการ


5
-1 นี่มันแย่มากและไร้สาระ คุณไม่ได้กำหนดคลาสที่จะใช้เป็นวัตถุ คุณจะไม่สามารถเข้าถึงการเรียนโดยไม่ต้องน่าเกลียดมากเหมือนหรือtype(wat) wat.__class__หากคุณต้องการที่จะบรรลุเป้าหมายนี้ให้กำหนดชั้นเรียนและยกตัวอย่างทันทีดีกว่าโดยไม่ต้องยุ่งกับมัณฑนากร
0xc0de

2
ทำไมคุณจำเป็นต้องรู้ใช้คลาสเดี่ยว? เพียงแค่ใช้วัตถุเดี่ยว ..
Tolli

1
ไม่ใช่รูปแบบซิงเกิลดังนั้น IMO ฟังก์ชันควรตั้งชื่อต่างกัน
GingerPlusPlus

8
Wikipedia: "รูปแบบซิงเกิลเป็นรูปแบบการออกแบบที่ จำกัด การสร้างอินสแตนซ์ของคลาสให้กับวัตถุหนึ่งเดียว" ฉันจะบอกว่าวิธีการแก้ปัญหาของฉันทำแค่นั้น โอเคฉันเดาว่าจะทำได้wat2 = type(wat)()แต่นี่คืองูใหญ่เราทุกคนยินยอมผู้ใหญ่และทุกอย่าง คุณไม่สามารถรับประกันได้ว่าจะมีเพียงตัวอย่างเดียว แต่คุณสามารถรับประกันได้ว่าหากผู้คนทำอย่างที่สองมันจะดูน่าเกลียดและ - ถ้าพวกเขาดีคนที่อยู่ในสภาพดี - เป็นสัญญาณเตือนพวกเขา ฉันพลาดอะไรไป
Jonas Kölker

7

ตรวจสอบคำถาม Stack Overflow มีวิธีที่ง่ายและสง่างามในการกำหนดซิงเกิลตันใน Python หรือไม่? ด้วยโซลูชั่นที่หลากหลาย

ผมขออยากแนะนำให้ไปดูการเจรจาอเล็กซ์เทลในรูปแบบการออกแบบในหลาม: ส่วนที่ 1และส่วนที่ 2 โดยเฉพาะอย่างยิ่งในตอนที่ 1 เขาพูดถึงวัตถุเดี่ยว / รัฐที่ใช้ร่วมกัน


2
แม้ว่านี่จะไม่ใช่คำตอบสำหรับคำถามของฉัน แต่แหล่งข้อมูลที่คุณชี้ไปนั้นมีประโยชน์มาก ฉันขอให้ +1 ของคุณด้วยความ
ซุกซน

3

นี่คือการใช้งานซิงเกิลตันของฉันเอง สิ่งที่คุณต้องทำคือตกแต่งชั้นเรียน เพื่อให้ได้ซิงเกิลคุณต้องใช้Instanceวิธีนี้ นี่คือตัวอย่าง:

   @Singleton
   class Foo:
       def __init__(self):
           print 'Foo created'

   f = Foo() # Error, this isn't how you get the instance of a singleton

   f = Foo.Instance() # Good. Being explicit is in line with the Python Zen
   g = Foo.Instance() # Returns already created instance

   print f is g # True

และนี่คือรหัส:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Other than that, there are
    no restrictions that apply to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    Limitations: The decorated class cannot be inherited from.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

2
มันไม่ได้เป็นซิงเกิลที่แท้จริง SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())ควรพิมพ์Trueในซิงเกิลจริง ด้วยการพิมพ์รหัสของคุณFalse
GingerPlusPlus

@GingerPlusPlus ฉันทราบถึงข้อ จำกัด เล็กน้อย แต่ไม่ใช่ข้อ จำกัด ที่คุณระบุ ขอบคุณที่พูดถึงมัน โชคไม่ดีที่ฉันไม่มีเวลาที่จะคิดวิธีแก้ปัญหานี้
พอล Manta

2

วิธีที่ 3 ดูเหมือนจะเรียบร้อยมาก แต่ถ้าคุณต้องการให้โปรแกรมของคุณทำงานทั้งPython 2และPython 3มันจะไม่ทำงาน แม้แต่การปกป้องชุดย่อยที่แยกกันด้วยการทดสอบสำหรับเวอร์ชัน Python ก็ล้มเหลวเนื่องจากรุ่น Python 3 มีข้อผิดพลาดทางไวยากรณ์ใน Python 2

ขอบคุณไมค์วัตคินส์: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ หากคุณต้องการให้โปรแกรมทำงานได้ทั้ง Python 2 และ Python 3 คุณต้องทำสิ่งต่อไปนี้

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]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

ฉันคิดว่า 'object' ในการกำหนดจะต้องถูกแทนที่ด้วย 'BaseClass' แต่ฉันไม่ได้ลองเลย (ฉันได้ลองใช้โค้ดดังที่แสดง)


แน่นอนว่านี่ไม่ใช่ metaclass - ใน python3 เพื่อใช้ metaclass เพื่อสร้าง MyClass ที่คุณจะทำclass MyClass(metaclass=Singleton)
theheadofabroom

mikewatkins.caลิงค์ (อย่างมีประสิทธิภาพ) เสีย
Peter Mortensen

1

นอกเหนือจากการเห็นด้วยกับข้อเสนอแนะ Pythonic ทั่วไปเกี่ยวกับการมีโมดูลระดับโลกวิธีการเกี่ยวกับเรื่องนี้:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs)
                class_w._instance._sealed = False
            return class_w._instance
        def __init__(self, *args, **kwargs):
            if self._sealed:
                return
            super(class_w, self).__init__(*args, **kwargs)
            self._sealed = True
    class_w.__name__ = class_.__name__
    return class_w

@singleton
class MyClass(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

ผลลัพธ์คือ:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

คุณสมบัติของแอ_sealedททริบิวคืออะไร? เท่าที่ฉันเห็นสิ่งนี้ไม่ได้ทำอะไรเลยเหรอ? มีบางอย่างที่กวนใจฉันเกี่ยวกับสิ่งนี้ที่บอกว่ามันไม่ควรทำงานได้ดี ... ฉันจะทำการทดสอบเชิงเปรียบเทียบในสัปดาห์นี้
theheadofabroom

_sealed ทำให้แน่ใจว่าinitของคุณทำงานเพียงครั้งเดียว ฉันไม่เห็นจุดใดเลยว่าทำไมมันควรทำงานได้แย่กว่ามัณฑนากรเหมือนฟังก์ชั่นปกติ - ฟังก์ชั่นจะถูกดำเนินการเพียงครั้งเดียวต่อคลาสและส่งคืนคลาสที่สืบทอดใหม่
Guard

BTW การแก้ไขของคุณมีแท็บที่แยกการเยื้องคุณยังพูดว่า 'เรากำลังสร้าง 2 คลาส' - คุณหมายถึงการที่เรากำลังสร้าง '1 คลาสพิเศษ' หรือไม่
ยาม

ใช่หนึ่งคลาสพิเศษคือสิ่งที่ฉันหมายถึง ฉันตั้งใจจะรวมสิ่งต่างๆไว้ในที่__init__จะถูกเรียกทุกครั้งที่มีการเริ่มต้น เพียงแค่ 'กำลังเริ่มต้นใน class.method' ง่าย ๆ สำหรับการเยื้อง - คุณใช้แท็บและช่องว่าง - ฉันแก้ไขส่วนใหญ่แล้ว แต่ดูเหมือนจะพลาดไปหนึ่งอันถ้าคุณต้องการรับ (แค่ตรวจสอบบันทึกการแก้ไข)
theheadofabroom

กำลังเริ่มต้น : แน่นอนว่ามันขึ้นอยู่กับคุณฉันแค่พยายามเลียนแบบพฤติกรรมแบบซิงเกิลในภาษาอื่น ๆ โดยที่โค้ดคอนสตรัคเตอร์ (ซึ่งไม่ได้เรียกว่าinitแต่ใกล้เคียงกันมาก) จะถูกเรียกเพียงครั้งเดียวถ้าคุณต้องการให้initเป็น เรียกทุกครั้งเพียงฆ่าการอ้างอิงทั้งหมดไปยังช่องว่าง / แท็บ _sealed ใหม่ - ดีแล้ว emacs ของฉันต้องแก้ไข อย่างไรก็ตามข้างบนเป็นเวอร์ชันที่แก้ไขแล้ว
Guard

1

เกี่ยวกับสิ่งนี้:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

ใช้เป็นมัณฑนากรในคลาสที่ควรเป็นซิงเกิล แบบนี้:

@singleton
class MySingleton:
    #....

นี่คล้ายกับsingleton = lambda c: c()มัณฑนากรในคำตอบอื่น เช่นเดียวกับโซลูชันอื่นอินสแตนซ์เดียวเท่านั้นที่มีชื่อคลาส ( MySingleton) แต่ด้วยวิธีนี้คุณยังสามารถ "สร้าง" อินสแตนซ์ (ได้รับจริงเพียงตัวอย่าง) MySingleton()จากชั้นเรียนโดยการทำ นอกจากนี้ยังป้องกันไม่ให้คุณสร้างอินสแตนซ์เพิ่มเติมโดยทำtype(MySingleton)()(ซึ่งจะส่งคืนอินสแตนซ์เดียวกัน)


คุณไม่ได้กำหนดคลาสเพื่อใช้เป็นวัตถุ
0xc0de

1
เวลาที่คุณโทรหาทุกคนtype(MySingleton)(), MySingleton.__init__()ได้รับการเรียกและวัตถุที่ได้รับการเริ่มต้นหลายครั้ง; คุณสามารถแก้ไขได้เขียนในของคุณcls.__init__ = lambda self: pass singletonนอกจากนี้ยังเอาชนะcls.__call__ดูเหมือนไม่มีจุดหมายและแม้กระทั่งที่เป็นอันตราย - __call__กำหนดไว้ในบริบทนี้ถูกนำมาใช้เมื่อคุณเรียกไม่ได้เมื่อคุณเรียกMySingleton(any, list, of, arguments) type(MySingleton)(any, list, of, arguments)
GingerPlusPlus

@GingerPlusPlus ขอบคุณสำหรับการชี้ให้เห็นว่าได้รับการเรียกอีกครั้งเมื่อทำ__init__() type(MySingleton)()โซลูชันที่คุณเสนอ (เพิ่มcls.__init__ = lambda self: pass) ให้ข้อผิดพลาดทางไวยากรณ์เนื่องจากส่วนสุดท้ายของการแสดงออกแลมบ์ดาจะต้องมีการแสดงออกไม่ใช่คำสั่ง อย่างไรก็ตามการเพิ่มcls.__init__ = lambda self: Noneผลงานดังนั้นฉันจึงเพิ่มเข้าไปในคำตอบของฉัน
Tolli

1
@GingerPlusPlus __call__เกี่ยวกับการใช้งานของ ฉันตั้งใจจะทำทั้งสองอย่างtype(MySingleton)()และMySingleton()คืนค่าอินสแตนซ์ ดังนั้นจึงทำในสิ่งที่ฉันต้องการ คุณสามารถนึกถึง MySingleton เป็นชนิดของ singleton หรืออินสแตนซ์ของ singleton (หรือทั้งสองอย่าง)
Tolli

1

ฉันจะขว้างระเบิดเข้าไปในวงแหวน มันเป็นมัณฑนากรที่เรียบง่าย

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

ประโยชน์ฉันคิดว่ามันมีโซลูชันอื่น ๆ :

  • ชัดเจนและรัดกุม (ตาของฉัน; D)
  • การกระทำของมันถูกห่อหุ้มอย่างสมบูรณ์ YourClassคุณไม่จำเป็นต้องมีการเปลี่ยนแปลงเป็นสิ่งเดียวกับการดำเนินงานของ ซึ่งรวมถึงไม่จำเป็นต้องใช้ metaclass สำหรับคลาสของคุณ (โปรดทราบว่า metaclass ด้านบนอยู่ในโรงงานไม่ใช่คลาส "ของจริง")
  • มันไม่ได้พึ่งพาการทำแพทช์ลิงเลย
  • มันโปร่งใสสำหรับผู้โทร:
    • ผู้โทรยังคงนำเข้าYourClassได้ดูเหมือนเป็นคลาส (เพราะเป็น) และพวกเขาใช้งานได้ตามปกติ ไม่จำเป็นต้องปรับผู้โทรเข้ากับฟังก์ชั่นจากโรงงาน
    • สิ่งที่YourClass()อินสแตนซ์ยังคงเป็นอินสแตนซ์ที่แท้จริงของYourClassคุณที่ใช้งานไม่ใช่พร็อกซีใด ๆ ดังนั้นจึงไม่มีโอกาสเกิดผลข้างเคียงจากสิ่งนั้น
    • isinstance(instance, YourClass) และการดำเนินการที่คล้ายกันยังคงทำงานได้ตามที่คาดไว้ (แม้ว่าบิตนี้จะต้องใช้ abc ดังนั้นอย่ารวม Python <2.6)

ข้อเสียอย่างหนึ่งเกิดขึ้นกับฉัน: classmethods และ staticmethods ของคลาสจริงไม่สามารถเรียกได้อย่างโปร่งใสผ่านคลาสโรงงานที่ซ่อนไว้ ฉันใช้มันไม่ค่อยพอที่จะไม่เกิดขึ้นกับความต้องการนั้น แต่มันจะได้รับการแก้ไขอย่างง่ายดายโดยการใช้ metaclass แบบกำหนดเองบนโรงงานที่ใช้__getattr__()ในการมอบหมายการเข้าถึงคุณลักษณะทั้งหมดของ ish ให้กับคลาสจริง

รูปแบบที่เกี่ยวข้องที่ฉันพบว่ามีประโยชน์มากกว่าจริง ๆ (ไม่ใช่ว่าฉันกำลังบอกว่าสิ่งเหล่านี้จำเป็นต้องใช้บ่อยมาก) เป็นรูปแบบ "ที่ไม่ซ้ำ" ซึ่งการสร้างอินสแตนซ์ของคลาสด้วยการขัดแย้งกันทำให้ได้รับอินสแตนซ์เดียวกัน นั่นคือ "ซิงเกิลต่ออาร์กิวเมนต์" ด้านบนปรับให้เหมาะกับหลุมนี้และกระชับยิ่งขึ้น:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

จากทั้งหมดที่กล่าวมาฉันเห็นด้วยกับคำแนะนำทั่วไปว่าหากคุณคิดว่าคุณต้องการสิ่งใดสิ่งหนึ่งคุณควรหยุดสักครู่แล้วถามตัวเองว่าคุณทำจริงหรือไม่ 99% ของเวลา, YAGNI


1
  • หากต้องการมีหลายอินสแตนซ์ของคลาสเดียวกัน แต่หาก args หรือ kwargs แตกต่างกันสามารถใช้สิ่งนี้
  • อดีต
    1. หากคุณมีการserialสื่อสารในระดับการจัดการและเพื่อสร้างอินสแตนซ์ที่คุณต้องการส่งพอร์ตอนุกรมเป็นอาร์กิวเมนต์แล้วด้วยวิธีการแบบดั้งเดิมจะไม่ทำงาน
    2. ด้วยการใช้ decorators ที่กล่าวถึงข้างต้นเราสามารถสร้างคลาสได้หลายอินสแตนซ์ถ้า args นั้นแตกต่างกัน
    3. สำหรับ args เดียวกัน decorator จะส่งคืนอินสแตนซ์เดียวกันซึ่งถูกสร้างขึ้นแล้ว
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

0

รหัสขึ้นอยู่กับคำตอบของ Tolli

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

คำอธิบาย:

  1. สร้างคลาสใหม่สืบทอดจากที่กำหนดcls
    (จะไม่แก้ไขclsในกรณีที่มีคนต้องการตัวอย่างsingleton(list))

  2. สร้างตัวอย่าง ก่อนที่จะเอาชนะ__new__มันง่ายมาก

  3. ตอนนี้เมื่อเราสร้างอินสแตนซ์ได้ง่าย ๆ การแทนที่__new__โดยใช้ช่วงเวลาที่กำหนดวิธีการที่ผ่านมา
  4. ฟังก์ชันจะส่งกลับเฉพาะเมื่อมันเป็นสิ่งที่คาดว่าจะโทรมิฉะนั้นยกinstance เงื่อนไขจะไม่พบเมื่อมีคนพยายามที่จะสืบทอดจากชั้นตกแต่งTypeError

  5. หาก__new__()ผลตอบแทนที่เป็นตัวอย่างของclsแล้วตัวอย่างใหม่ของ__init__()วิธีการที่จะถูกเรียกเช่นที่ตัวเองเป็นตัวอย่างใหม่และข้อโต้แย้งที่เหลือเป็นเช่นเดียวกับที่ถูกส่งผ่านไปยัง__init__(self[, ...])__new__()

    instanceเริ่มต้นแล้วดังนั้นฟังก์ชันแทนที่__init__ด้วยฟังก์ชันที่ไม่ทำอะไรเลย

ดูการทำงานออนไลน์


0

มันคล้ายกับคำตอบโดย fab เล็กน้อย แต่ไม่เหมือนกันทุกประการ

สัญญาเดี่ยวไม่จำเป็นต้องที่เราจะสามารถที่จะเรียกตัวสร้างหลายครั้ง ในฐานะที่เป็นโสดควรจะสร้างครั้งเดียวเท่านั้นมันไม่ควรที่จะถูกสร้างขึ้นเพียงครั้งเดียว? "การปลอมแปลง" คอนสตรัคเตอร์ทำให้เนื้อหาบกพร่อง

ดังนั้นข้อเสนอแนะของฉันเป็นเพียงแค่นี้:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

สิ่งนี้ไม่ได้ออกกฎการใช้นวกรรมิกหรือฟิลด์instanceตามรหัสผู้ใช้:

if Elvis() is King.instance:

... หากคุณทราบแน่ชัดว่าElvisยังไม่มีการสร้างและสิ่งนั้นKingมี

แต่มันกระตุ้นให้ผู้ใช้ใช้theวิธีการแบบสากล:

Elvis.the().leave(Building.the())

หากต้องการทำให้สมบูรณ์คุณสามารถแทนที่__delattr__()ยกข้อยกเว้นหากมีความพยายามลบinstanceและแทนที่__del__()เพื่อให้มีข้อยกเว้น (ยกเว้นว่าเรารู้ว่าโปรแกรมกำลังจะสิ้นสุด ... )

การปรับปรุงเพิ่มเติม


ฉันขอขอบคุณผู้ที่ช่วยด้วยความคิดเห็นและการแก้ไขซึ่งยินดีต้อนรับ ขณะที่ฉันใช้ Jython สิ่งนี้ควรใช้งานได้ดีกว่าและปลอดภัยกับเธรด

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

จุดที่ควรทราบ:

  1. หากคุณไม่ได้คลาสย่อยจากวัตถุใน python2.x คุณจะได้รับคลาสแบบเก่าซึ่งไม่ได้ใช้ __new__
  2. เมื่อผสม__new__คุณต้องตกแต่งด้วย @classmethod หรือ__new__จะเป็นวิธีการอินสแตนซ์ที่ไม่ถูกผูกไว้
  3. สิ่งนี้อาจได้รับการปรับปรุงให้ดีขึ้นโดยการใช้ metaclass เนื่องจากจะทำให้คุณสามารถสร้างtheคลาสระดับคุณสมบัติได้instance

ในขณะที่นี่เป็นการตีความที่แตกต่างกันเล็กน้อยของรูปแบบซิงเกิลฉันค่อนข้างแน่ใจว่ามันยังคงใช้ได้แม้ว่าฉันอาจถูกล่อลวงให้ใช้__new__ มากกว่า__init__เพราะมันทำหน้าที่เกี่ยวกับคุณลักษณะของคลาสอย่างหมดจด ความแตกต่างระหว่างสิ่งนี้กับวิธีที่ 2 คือพยายามเริ่มต้นมากกว่าหนึ่งครั้งส่งคืนอินสแตนซ์เดียวหรือยกข้อยกเว้น ฉันคิดว่าฉันมีความสุขที่จะได้รับความพึงพอใจกับรูปแบบของซิงเกิลตันอันหนึ่งใช้งานได้ง่ายกว่าในขณะที่อีกอันนั้นชัดเจนกว่าว่าเป็นซิงเกิล
theheadofabroom

เห็นได้ชัดว่าการใช้ชื่อคลาสในการ__init__ป้องกันไม่ให้คลาสย่อย แต่ในขณะนี้ทำให้สิ่งต่าง ๆ ง่ายขึ้นก็ไม่จำเป็นต้องใช้
theheadofabroom

ขอบคุณ ... อ่าใช่อินสแตนซ์ที่สองชั่วขณะหนึ่งก่อนที่ข้อยกเว้นจะถูกโยนทิ้ง ฉันได้แก้ไข__init__เพื่อที่หวังว่านี้ควรจะ subclassable ...
ไมค์หนู

เจ๋งtheอาจได้ประโยชน์จากวิธีการเรียนด้วยเหตุผลที่คล้ายกัน
theheadofabroom

ใช่คุณพูดถูก. จากนั้นคุณสามารถมี SuperElvis subclass singleton และ (ตัวอย่าง) ImaginaryElvis subclass singleton ... และพวกมันสามารถอยู่ร่วมกันได้ ดูความคิดเพิ่มเติม โปรดปรับปรุงรหัสของฉัน
mike rodent

0

สายการบินหนึ่ง (ฉันไม่ได้ภูมิใจ แต่มันทำงานได้):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify

สิ่งนี้อาจทำงานได้ตราบใดที่คลาสของคุณยังไม่ได้ถูกนำเข้าไปยังโมดูลอื่น ๆ ...
Aran-Fey

จริง หากคุณสามารถจ่ายค่าใช้จ่ายในการลงทะเบียนเรียนคุณสามารถเรียกใช้ Myclass () ทันทีหลังจากที่คำจำกัดความของชั้นเรียนเพื่อหลีกเลี่ยงปัญหานี้
polvoazul

0

หากคุณไม่ต้องการการเริ่มต้นอย่างขี้เกียจของอินสแตนซ์ของ Singleton ต่อไปนี้ควรเป็นเรื่องง่ายและปลอดภัยสำหรับเธรด:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

วิธีAนี้เป็นการเริ่มต้นเดี่ยวที่นำเข้าโมดูล


0

บางทีฉันอาจจะคิดถึงรูปแบบซิงเกิล แต่วิธีแก้ปัญหาของฉันคือแบบเรียบง่ายและใช้งานได้จริง (pythonic?) รหัสนี้เต็มสองเป้าหมาย

  1. ทำอินสแตนซ์ของการFooเข้าถึงได้ทุกที่ (ทั่วโลก)
  2. Fooสามารถมีอินสแตนซ์เดียวเท่านั้น

นี่คือรหัส

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

เอาท์พุต

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

0

หลังจากดิ้นรนกับสิ่งนี้เป็นครั้งคราวในที่สุดฉันก็พบสิ่งต่อไปนี้เพื่อให้วัตถุการตั้งค่าจะถูกโหลดเพียงครั้งเดียวเมื่อเรียกใช้จากโมดูลที่แยกต่างหาก metaclass อนุญาตให้เก็บคลาสอินสแตนซ์ระดับโลกไว้ใน builtin dict ซึ่งในปัจจุบันดูเหมือนจะเป็นวิธีที่เรียบร้อยที่สุดในการจัดเก็บโปรแกรมที่เหมาะสมทั่วโลก

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...

-1

ฉันจำไม่ได้ว่าฉันเจอทางแก้ปัญหานี้ที่ไหน แต่ฉันคิดว่ามันเป็น 'หรูหรา' ที่สุดจากมุมมองที่ไม่ใช่ Python-expert ของฉัน:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

ทำไมฉันถึงชอบสิ่งนี้? ไม่มีมัณฑนากรไม่มีคลาสเมต้าไม่มีการสืบทอดหลายอย่าง ... และถ้าคุณตัดสินใจว่าคุณไม่ต้องการให้เป็นซิงเกิลอีกต่อไปเพียงแค่ลบ__new__เมธอด ในขณะที่ฉันยังใหม่กับ Python (และ OOP โดยทั่วไป) ฉันคาดหวังว่าจะมีคนตั้งฉันให้ตรงเกี่ยวกับสาเหตุที่สิ่งนี้เป็นวิธีการที่แย่มาก?


2
ทำไมนี่เป็นวิธีที่แย่มาก? __new__เมื่อคุณต้องการสร้างชั้นเดี่ยวอื่นคุณต้องคัดลอกและวาง อย่าซ้ำตัวเอง
GingerPlusPlus

นอกจากนี้ทำไมสิ่งใหม่ของคุณ*argsถึง**kwargsและแล้วไม่ทำอะไรกับพวกเขา ผ่านพวกเขาเข้ามาdict.__new__ทางนี้: dict.__new__(cls, *args, **kwargs).
GingerPlusPlus

นี่จะเรียก__init__เมธอดทุกครั้งที่เรียกคลาส หาก__init__วิธีการของคุณทำอะไรคุณจะสังเกตเห็นปัญหา เมื่อใดก็ตามที่คุณทำSomeSingleton()สถานะของซิงเกิลของคุณจะถูกรีเซ็ตโดย__init__วิธีการ
Aran-Fey

-2

นี่เป็นวิธีที่ฉันต้องการใช้งานซิงเกิลตัน:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here

1
นี่ไม่ใช่ซิงเกิลอย่างเคร่งครัดเนื่องจากอนุญาตให้มีคลาสมากกว่าหนึ่งอินสแตนซ์อยู่ การปรับปรุงจะทำให้ชั้นเรียนไม่สามารถเริ่มต้นได้และให้วิธีการเรียนทั้งหมดดำเนินการกับคุณลักษณะของชั้นเรียน
theheadofabroom

-2

คำตอบนี้น่าจะไม่ใช่สิ่งที่คุณกำลังมองหา ฉันต้องการซิงเกิลตันในแง่ที่ว่าวัตถุนั้นมีตัวตนของมันเพื่อเปรียบเทียบกับ ในกรณีของฉันมันถูกนำมาใช้เป็นค่า Sentinel คำตอบนั้นง่ายมากทำสิ่งใดก็ตามmything = object()และโดยธรรมชาติของงูเหลือมเพียงสิ่งนั้นเท่านั้นที่จะมีตัวตนของมัน

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

ฉันได้เรียนรู้ว่าโมดูลสามารถนำเข้าได้หลายครั้งจริง ๆ ในกรณีเช่นนี้เป็นเพียงซิงเกิลท้องถิ่นซึ่งไม่ได้เป็นซิงเกิลเดียวในทุกความสามารถ
ThorSummoner

คุณสามารถอธิบายรายละเอียดเกี่ยวกับวิธีนำเข้าโมดูลได้หลายครั้งได้อย่างไร ครั้งเดียวที่ฉันเห็นว่าเมื่อมีข้อยกเว้นเกิดขึ้นในขณะที่โหลดโมดูลผู้ใช้อาจยังโหลดโมดูลในภายหลัง แต่ผลข้างเคียงจะเกิดขึ้นแล้วดังนั้นการดำเนินการบางอย่างอาจถูกดำเนินการเป็นครั้งที่สอง
sleblanc

เมื่อโมดูลได้รับการโหลดอย่างเต็มที่ผมไม่เห็นวิธีที่จะมีการเรียกใช้โมดูลนี้อีกครั้งนอกเหนือจากการบีบบังคับล่ามในการทำโดยใช้หรือeval importlib.reload
sleblanc

-3

วิธีแก้ปัญหานี้ทำให้เกิดมลภาวะเนมสเปซในระดับโมดูล (คำจำกัดความสามคำแทนที่จะเป็นเพียงหนึ่งข้อ) แต่ฉันพบว่าง่ายต่อการติดตาม

ฉันต้องการที่จะสามารถเขียนบางสิ่งเช่นนี้ (การกำหนดค่าเริ่มต้นขี้เกียจ) แต่น่าเสียดายที่ชั้นเรียนไม่สามารถใช้ได้ในเนื้อหาของคำจำกัดความของตนเอง

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

เนื่องจากเป็นไปไม่ได้เราสามารถแยกการกำหนดค่าเริ่มต้นและอินสแตนซ์แบบคงที่ใน

เริ่มต้นกระตือรือร้น:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

การเริ่มต้น Lazy:

เริ่มต้นกระตือรือร้น:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.