Tạo một singleton trong Python


946

Câu hỏi này không dành cho cuộc thảo luận về việc liệu mẫu thiết kế singleton có được mong muốn hay không, là một mẫu chống, hoặc cho bất kỳ cuộc chiến tôn giáo nào, nhưng để thảo luận về cách thức mẫu này được triển khai tốt nhất trong Python theo cách phù hợp nhất. Trong trường hợp này, tôi định nghĩa 'hầu hết pythonic' có nghĩa là nó tuân theo 'nguyên tắc ít ngạc nhiên nhất' .

Tôi có nhiều lớp sẽ trở thành singletons (trường hợp sử dụng của tôi là cho một logger, nhưng điều này không quan trọng). Tôi không muốn làm lộn xộn một vài lớp có thêm kẹo cao su khi tôi có thể chỉ cần kế thừa hoặc trang trí.

Phương pháp tốt nhất:


Cách 1: Một người trang trí

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

Ưu

  • Trang trí là phụ gia theo cách thường trực quan hơn nhiều kế thừa.

Nhược điểm

  • Trong khi các đối tượng được tạo bằng MyClass () sẽ là các đối tượng singleton thực sự, thì chính MyClass là một hàm aa, không phải là một lớp, vì vậy bạn không thể gọi các phương thức lớp từ nó. Cũng để m = MyClass(); n = MyClass(); o = type(n)();sau đóm == n && m != o && n != o

Cách 2: Một lớp cơ sở

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

Ưu

  • Đó là một lớp học thực sự

Nhược điểm

  • Đa thừa kế - eugh! __new__có thể được ghi đè trong quá trình thừa kế từ lớp cơ sở thứ hai? Người ta phải suy nghĩ nhiều hơn là cần thiết.

Cách 3: Một siêu dữ liệu

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

Ưu

  • Đó là một lớp học thực sự
  • Tự động bảo vệ quyền thừa kế
  • Sử dụng __metaclass__cho mục đích đúng đắn của nó (và làm cho tôi biết về nó)

Nhược điểm

  • Có ai không

Phương pháp 4: trang trí trả về một lớp có cùng tên

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

Ưu

  • Đó là một lớp học thực sự
  • Tự động bảo vệ quyền thừa kế

Nhược điểm

  • Không có chi phí để tạo mỗi lớp mới? Ở đây chúng tôi đang tạo hai lớp cho mỗi lớp chúng tôi muốn tạo một singleton. Trong khi điều này là tốt trong trường hợp của tôi, tôi lo lắng rằng điều này có thể không mở rộng. Tất nhiên, có một vấn đề tranh luận là liệu nó có quá dễ dàng để mở rộng mô hình này không ...
  • Điểm của _sealedthuộc tính là gì
  • Không thể gọi các phương thức cùng tên trên các lớp cơ sở bằng cách sử dụng super()vì chúng sẽ lặp lại. Điều này có nghĩa là bạn không thể tùy chỉnh __new__và không thể phân lớp một lớp cần bạn gọi tới __init__.

Phương pháp 5: một mô-đun

một tập tin mô-đun singleton.py

Ưu

  • Đơn giản là tốt hơn phức tạp

Nhược điểm


12
Ba kỹ thuật khác: sử dụng một mô-đun thay thế (thường - nói chung, tôi nghĩ - đây là một mô hình phù hợp hơn cho Python nhưng nó phụ thuộc một chút vào những gì bạn đang làm với nó); làm một ví dụ duy nhất và đối phó với nó thay vào đó ( foo.xhoặc nếu bạn nhấn mạnh Foo.xthay vì Foo().x); sử dụng các thuộc tính lớp và các phương thức tĩnh / lớp ( Foo.x).
Chris Morgan

10
@ChrisMorgan: Nếu bạn chỉ sử dụng các phương thức lớp / tĩnh, thì đừng bận tâm đến việc tạo một lớp, thực sự.
Cat Plus Plus

2
@Cat: Hiệu ứng tương tự nhau, tuy nhiên lý do đằng sau việc tạo ra một biến toàn cục có thể chỉ là về bất cứ điều gì, bao gồm cả việc không biết gì hơn. Tại sao một người tạo ra một singleton? Nếu bạn phải hỏi bạn không nên ở đây. Nhà thám hiểm này không chỉ nhiều pythonic, mà làm cho việc bảo trì đơn giản hơn rất nhiều. Có singletons là đường cú pháp cho toàn cầu, nhưng sau đó các lớp là đường cú pháp cho cả đống thứ khó coi và tôi không nghĩ ai sẽ nói với bạn rằng bạn luôn tốt hơn nếu không có chúng.
theheadofabroom

14
@BiggAl: Singletons không phải là Pythonic, bất kể bạn thực hiện chúng như thế nào. Chúng là một dấu hiệu của một thiết kế thiếu sót tốt nhất.
Cat Plus Plus

8
Tình cảm chống Signletons là lập trình sùng bái hàng hóa ở mức tồi tệ nhất của nó. Tương tự với những người nghe (một số người bận tâm thực sự đọc) "Tuyên bố Goto được coi là có hại" và nghĩ rằng gotos là một dấu hiệu của mã xấu bất kể bối cảnh.
Hejazzman

Câu trả lời:


658

Sử dụng siêu dữ liệu

Tôi muốn giới thiệu Phương pháp # 2 , nhưng tốt hơn hết là bạn nên sử dụng siêu dữ liệu hơn là lớp cơ sở. Đây là một triển khai mẫu:

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

Hoặc trong Python3

class Logger(metaclass=Singleton):
    pass

Nếu bạn muốn chạy __init__mỗi khi lớp được gọi, hãy thêm

        else:
            cls._instances[cls].__init__(*args, **kwargs)

để iftuyên bố trong Singleton.__call__.

Một vài lời về metaclass. Một siêu dữ liệu là lớp của một lớp ; đó là, một lớp là một thể hiện của siêu dữ liệu của nó . Bạn tìm siêu dữ liệu của một đối tượng trong Python với type(obj). Các lớp phong cách mới bình thường là loại type. Loggertrong đoạn mã trên sẽ là kiểu class 'your_module.Singleton', giống như thể hiện (chỉ) của kiểu Loggersẽ là kiểu class 'your_module.Logger'. Khi bạn gọi logger với Logger(), trước tiên Python hỏi siêu dữ liệu về Logger, Singletonphải làm gì, cho phép tạo cá thể được làm trống trước. Quá trình này giống như Python hỏi một lớp phải làm gì bằng cách gọi __getattr__khi bạn tham chiếu một trong các thuộc tính của nó bằng cách thực hiện myclass.attribute.

Một siêu dữ liệu về cơ bản quyết định định nghĩa của một lớp có nghĩa là gì và làm thế nào để thực hiện định nghĩa đó. Xem ví dụ http://code.activestate.com/recipes/498149/ , về cơ bản tái tạo các kiểu chữ C structtrong Python bằng siêu dữ liệu. Chủ đề Một số trường hợp sử dụng (cụ thể) cho metaclass là gì? cũng cung cấp một số ví dụ, nhìn chung chúng dường như có liên quan đến lập trình khai báo, đặc biệt là được sử dụng trong ORM.

Trong tình huống này, nếu bạn sử dụng Phương thức # 2 của mình và một lớp con xác định một __new__phương thức, nó sẽ được thực thi mỗi khi bạn gọi SubClassOfSingleton()- bởi vì nó chịu trách nhiệm gọi phương thức trả về thể hiện được lưu trữ. Với một siêu dữ liệu, nó sẽ chỉ được gọi một lần , khi thể hiện duy nhất được tạo. Bạn muốn tùy chỉnh ý nghĩa của việc gọi lớp , được quyết định bởi loại của nó.

Nói chung, nó có ý nghĩa để sử dụng một siêu dữ liệu để thực hiện một singleton. Một singleton là đặc biệt bởi vì chỉ được tạo một lần và siêu dữ liệu là cách bạn tùy chỉnh việc tạo một lớp . Sử dụng siêu dữ liệu cho phép bạn kiểm soát nhiều hơn trong trường hợp bạn cần tùy chỉnh các định nghĩa lớp singleton theo các cách khác.

Các singletons của bạn sẽ không cần nhiều kế thừa (vì siêu dữ liệu không phải là lớp cơ sở), nhưng đối với các lớp con của lớp được tạo sử dụng nhiều kế thừa, bạn cần đảm bảo rằng lớp singleton là lớp đầu tiên / ngoài cùng với siêu lớp xác định lại __call__Điều này rất khó có thể là một vấn đề. Dict thể hiện không nằm trong không gian tên của cá thể nên nó sẽ không vô tình ghi đè lên nó.

Bạn cũng sẽ nghe rằng mô hình đơn lẻ vi phạm "Nguyên tắc trách nhiệm duy nhất" - mỗi lớp chỉ nên làm một việc . Bằng cách đó, bạn không phải lo lắng về việc làm xáo trộn một điều mà mã làm nếu bạn cần thay đổi một thứ khác, bởi vì chúng riêng biệt và được gói gọn. Việc thực hiện siêu dữ liệu vượt qua bài kiểm tra này . Siêu dữ liệu chịu trách nhiệm thực thi mẫu và lớp được tạo và các lớp con không cần phải biết rằng chúng là singletons . Phương thức # 1 thất bại trong bài kiểm tra này, như bạn đã lưu ý với "Bản thân MyClass là một hàm aa, không phải là một lớp, vì vậy bạn không thể gọi các phương thức lớp từ nó."

Phiên bản tương thích Python 2 và 3

Viết một cái gì đó hoạt động trong cả Python2 và 3 yêu cầu sử dụng sơ đồ phức tạp hơn một chút. Vì siêu dữ liệu thường là các lớp con của loại type, nên có thể sử dụng một lớp để tự động tạo một lớp cơ sở trung gian trong thời gian chạy với nó như là siêu dữ liệu của nó và sau đó sử dụng làm Singletonlớp cơ sở của lớp cơ sở công cộng . Thật khó để giải thích hơn là làm, như minh họa tiếp theo:

# 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

Một khía cạnh mỉa mai của phương pháp này là nó sử dụng phân lớp để thực hiện siêu dữ liệu. Một lợi thế có thể là, không giống như với một siêu dữ liệu thuần túy, isinstance(inst, Singleton)sẽ trở lại True.

Đính chính

Ở một chủ đề khác, có lẽ bạn đã nhận thấy điều này, nhưng việc triển khai lớp cơ sở trong bài viết gốc của bạn là sai. _instancescần được tham chiếu trên lớp , bạn cần sử dụng super()hoặc bạn đang đệ quy__new__thực sự là một phương thức tĩnh mà bạn phải truyền lớp đó , không phải là một phương thức lớp, vì lớp thực tế chưa được tạo khi nó được gọi là. Tất cả những điều này cũng sẽ đúng cho việc triển khai siêu dữ liệu.

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()

Trang trí trở lại một lớp học

Ban đầu tôi đã viết một bình luận nhưng nó quá dài, vì vậy tôi sẽ thêm nó vào đây. Phương pháp số 4 tốt hơn so với phiên bản trang trí khác, nhưng nó có nhiều mã hơn mức cần thiết cho một người độc thân và không rõ ràng về những gì nó làm.

Các vấn đề chính xuất phát từ lớp là lớp cơ sở của riêng nó. Đầu tiên, không có gì lạ khi có một lớp là một lớp con của một lớp gần giống với cùng tên chỉ tồn tại trong __class__thuộc tính của nó ? Điều này cũng có nghĩa là bạn không thể xác định bất kỳ phương pháp mà gọi phương thức cùng tên trên lớp cơ sở của họ với super()bởi vì họ sẽ recurse. Điều này có nghĩa là lớp của bạn không thể tùy chỉnh __new__và không thể xuất phát từ bất kỳ lớp nào cần __init__gọi chúng.

Khi nào nên sử dụng mẫu singleton

Trường hợp sử dụng của bạn là một trong những ví dụ tốt hơn về việc muốn sử dụng một singleton. Bạn nói trong một trong những bình luận "Đối với tôi, việc đăng nhập dường như luôn là một ứng cử viên tự nhiên cho Singletons." Bạn hoàn toàn đúng .

Khi mọi người nói singletons là xấu, lý do phổ biến nhất là họ ở trạng thái chia sẻ ngầm . Trong khi với các biến toàn cục và nhập mô-đun cấp cao nhất là trạng thái chia sẻ rõ ràng , các đối tượng khác được truyền xung quanh thường được khởi tạo. Đây là một điểm tốt, với hai ngoại lệ .

Điều đầu tiên, và một điều được nhắc đến ở nhiều nơi khác nhau, là khi các singletons không đổi . Việc sử dụng các hằng số toàn cầu, đặc biệt là enums, được chấp nhận rộng rãi và được coi là lành mạnh bởi vì dù thế nào, không ai trong số người dùng có thể gây rối cho bất kỳ người dùng nào khác . Điều này cũng đúng cho một singleton liên tục.

Ngoại lệ thứ hai, được nhắc đến ít hơn, thì ngược lại - khi singleton chỉ là phần chìm dữ liệu , không phải là nguồn dữ liệu (trực tiếp hoặc gián tiếp). Đây là lý do tại sao logger cảm thấy như một cách sử dụng "tự nhiên" cho singletons. Vì những người dùng khác nhau không thay đổi logger theo cách mà những người dùng khác sẽ quan tâm, nên không có trạng thái chia sẻ thực sự . Điều này phủ nhận đối số chính chống lại mẫu đơn và làm cho chúng trở thành một lựa chọn hợp lý vì dễ sử dụng cho nhiệm vụ.

Đây là một trích dẫn từ http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :

Bây giờ, có một loại Singleton là OK. Đó là một singleton nơi tất cả các đối tượng có thể tiếp cận là bất biến. Nếu tất cả các đối tượng là bất biến hơn Singleton không có trạng thái toàn cầu, vì mọi thứ đều không đổi. Nhưng thật dễ dàng để biến loại đơn này thành một loại đột biến, nó rất trơn trượt. Do đó, tôi cũng chống lại những Singletons này, không phải vì họ xấu, mà vì họ rất dễ xấu. (Như một lưu ý phụ, bảng liệt kê Java chỉ là những loại singletons. Miễn là bạn không đặt trạng thái vào bảng liệt kê của mình, bạn vẫn ổn, vì vậy xin đừng.)

Một loại Singletons khác, có thể chấp nhận được là những loại không ảnh hưởng đến việc thực thi mã của bạn, Chúng không có "tác dụng phụ". Ghi nhật ký là ví dụ hoàn hảo. Nó được tải với Singletons và nhà nước toàn cầu. Có thể chấp nhận được (vì nó sẽ không làm tổn thương bạn) vì ứng dụng của bạn không hoạt động khác nhau cho dù một trình ghi nhật ký đã cho phép hay chưa. Thông tin ở đây chảy theo một cách: Từ ứng dụng của bạn vào bộ ghi chép. Ngay cả logger nghĩ là trạng thái toàn cầu vì không có thông tin nào chuyển từ logger vào ứng dụng của bạn, logger có thể được chấp nhận. Bạn vẫn nên tiêm logger nếu bạn muốn kiểm tra của mình để xác nhận rằng một cái gì đó đang được ghi lại, nhưng nói chung Loggers không có hại mặc dù có đầy đủ trạng thái.


5
Không, singletons không bao giờ tốt. Ghi nhật ký có thể là một ứng cử viên tốt để trở thành một người toàn cầu (khủng khiếp như họ), nhưng chắc chắn không phải là đơn lẻ.
Cat Plus Plus

11
Nhìn vào googletesting.blogspot.com/2008/08/ . Nó thường là chống độc thân (vì lý do chính đáng) nhưng nó có một lời giải thích tốt về lý do tại sao những người độc thân và những người độc thân không có tác dụng phụ không gặp phải những vấn đề tương tự, nếu bạn cẩn thận. Tôi sẽ trích dẫn nó một chút ở cuối bài viết của tôi.
agf

4
Vấn đề của tôi với singletons là tiền đề ngu ngốc của "chỉ một trường hợp". Đó và tấn vấn đề an toàn chủ đề. Và phụ thuộc che giấu. Globals là xấu, và singletons chỉ là toàn cầu với nhiều vấn đề hơn.
Cat Plus Plus

4
@Cat Có những cách sử dụng rất tốt cho singletons. Khởi tạo nhanh các mô-đun phần cứng (đặc biệt là trong các ứng dụng luồng đơn) là một trong số đó (nhưng các singletons an toàn luồng cũng tồn tại).
Paul Manta

3
@Alcott __new__trong một siêu dữ liệu là khi lớp này mới - khi nó được định nghĩa, không phải khi thể hiện sẽ là mới. Gọi lớp ( MyClass()) là thao tác bạn muốn ghi đè, không phải định nghĩa của lớp. Nếu bạn thực sự muốn hiểu cách Python hoạt động, điều tốt nhất bạn có thể làm (ngoài việc tiếp tục sử dụng nó) là đọc docs.python.org/reference/datamodel.html . Một tài liệu tham khảo tốt về metaclass là eli.thegreenplace.net/2011/08/14/python-metaclass-by-example . Một bài viết hay về singletons là loạt bài từ blog google tôi đã liên kết trong câu trả lời này.
agf

91
class Foo(object):
     pass

some_global_variable = Foo()

Các mô-đun chỉ được nhập một lần, mọi thứ khác đều bị lật đổ. Đừng sử dụng singletons và cố gắng không sử dụng toàn cầu.


14
Tại sao bạn nói "Đừng sử dụng singletons"? Có lý do gì không?
Alcott

3
Điều này sẽ không hoạt động nếu singleton phải được ngâm. Sử dụng ví dụ bạn đã đưa ra:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
splititherzero

4
@dividitherzero: istoán tử kiểm tra sự bằng nhau của con trỏ. Tôi sẽ khá ngạc nhiên --- đến mức gọi nó là một lỗi --- nếu pickle.loadstrả về một tham chiếu đến một đối tượng tồn tại trước đó thay vì một tham chiếu đến một đối tượng mới được tạo. Do đó, kiểm tra xem liệu s is s1có cho bạn biết bất cứ điều gì về sự phù hợp của việc sử dụng các mô-đun như các singletons hay không.
Jonas Kölker

1
@ JonasKölker đã pickle.loads()làm điều đó rồi, ví dụ như đối với boolNoneType. pickle.loads(pickle.dumps(False)) is Falsesản lượngTrue
Dan Passaro

2
@ leo-the-hưng: điểm công bằng; Tuy nhiên, đó chỉ là một tác dụng phụ của Python thực tập các đối tượng True, FalseNone, và không có gì để làm với các đằng sau đang pickle.loads. Ngoài ra, chỉ an toàn cho các đối tượng chỉ đọc. Nếu pickle.loadsđược trả về một tham chiếu đến một đối tượng có thể sửa đổi đã có sẵn , chẳng hạn như một mô-đun, đó sẽ là một lỗi. (Và vì vậy tôi đứng ngụ ý của tôi rằng mã ví dụ dividebyzero của không chứng minh bất cứ điều gì.)
Jonas Kölker

69

Sử dụng một mô-đun. Nó chỉ được nhập một lần. Xác định một số biến toàn cục trong đó - chúng sẽ là 'thuộc tính' của singleton. Thêm một số chức năng - 'phương thức' của singleton.


11
Vì vậy, những gì bạn kết thúc là ... Không phải là một lớp học. Bạn không thể sử dụng nó như một lớp, bạn không thể dựa vào các lớp khác, bạn sử dụng cú pháp nhập và đột nhiên bạn mất tất cả các lợi ích của OOP ...
theheadofabroom

16
nếu bạn có thể căn cứ các lớp khác vào nó, thì nó có thể không phải là một singleton. bạn có thể tạo một trong các lớp dẫn xuất, nhưng cũng là một trong các lớp cơ sở, nhưng lớp dẫn xuất cũng là một thành viên của cơ sở, và bạn có hai trong số các cơ sở, bạn nên sử dụng lớp nào?
Độc thân Khuyến khích

Điều này không hoạt động trên các mô-đun. Trong mô-đun "chính" của tôi, tôi đặt một giá trị. Sau đó tôi tham chiếu nó trong một mô-đun khác và null của nó. Thở dài.
Paul Kenjora

1
@PaulKenjora Bạn phải có lỗi trong mã của mình. Nếu bạn xác định một biến toàn cục trong một mô-đun, khi bạn truy cập nó từ một mô-đun khác, nó sẽ có giá trị.
warvariuc 30/03/2017

Nó có giá trị nhưng khi tôi thay đổi thì nó không được bảo tồn. Tôi đã kết thúc việc sử dụng một lớp với các thuộc tính trong một mô-đun hoạt động. Các loại đơn giản như toàn cầu không hoạt động với tôi (chúng bị mất giá trị ngay khi phạm vi thay đổi).
Paul Kenjora

29

Bạn có thể không bao giờ cần một singleton trong Python. Chỉ cần xác định tất cả dữ liệu và chức năng của bạn trong một mô-đun và bạn có một đơn vị thực tế.

Nếu bạn thực sự cần phải có một lớp học đơn thì tôi sẽ đi với:

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

my_singleton = My_Singleton()

Để sử dụng:

from mysingleton import my_singleton
my_singleton.foo()

Trong đó mysingleton.py là tên tệp của bạn mà My_Singleton được xác định. Điều này hoạt động vì sau lần đầu tiên tệp được nhập, Python không thực thi lại mã.


4
Chủ yếu là đúng, nhưng đôi khi điều đó là không đủ. Ví dụ, tôi có một dự án có nhu cầu đăng nhập tức thời của nhiều lớp ở cấp DEBUG. Tôi cần các tùy chọn dòng lệnh được phân tích cú pháp khi khởi động để đặt mức ghi nhật ký do người dùng chỉ định trước khi các lớp đó được khởi tạo. Khởi tạo cấp mô-đun làm cho vấn đề đó. Có thể tôi có thể cấu trúc ứng dụng một cách cẩn thận để tất cả các lớp đó không được nhập cho đến khi quá trình xử lý CLI được thực hiện, nhưng cấu trúc tự nhiên của ứng dụng của tôi quan trọng hơn việc tuân thủ giáo điều là "singletons là xấu", vì chúng có thể thực hiện khá sạch sẽ.
CryingCyclops

nếu bạn đã kiểm tra mã của mình trong khi vá my_singleton, điều đó có khả thi không? vì my_singleton này có thể được khởi tạo trong một số mô-đun khác.
Naveen

@Naveen - my_singleton là một đối tượng. Nếu bạn "vá" thì sự thay đổi đó sẽ ảnh hưởng đến tất cả các tài liệu tham khảo trong tương lai, ngay cả trong các mô-đun khác.
Alan Dyke

16

Đây là một lót cho bạn:

singleton = lambda c: c()

Đây là cách bạn sử dụng nó:

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

assert wat.get_x() == 1

Đối tượng của bạn được ngay lập tức háo hức. Điều này có thể hoặc không thể là những gì bạn muốn.


5
-1. Điều này rất xấu và ngớ ngẩn. Bạn không định nghĩa một lớp để sử dụng làm đối tượng. Bạn không còn có thể truy cập lớp mà không xấu xí như type(wat)hay wat.__class__. Nếu bạn thực sự muốn đạt được điều này, tốt hơn nên xác định lớp và khởi tạo nó ngay lập tức, không cần phải gây rối với trình trang trí.
0xc0de

2
Tại sao bạn cần biết sử dụng lớp của một singleton? Chỉ cần sử dụng đối tượng singleton ..
Tolli

1
Đây không phải là mẫu đơn , vì vậy IMO chức năng nên được đặt tên khác.
GingerPlusPlus

8
Wikipedia: "mẫu đơn là một mẫu thiết kế giới hạn việc khởi tạo một lớp đối với một đối tượng". Tôi sẽ nói rằng giải pháp của tôi làm điều đó. Được rồi, tôi đoán người ta có thể làm wat2 = type(wat)(), nhưng đây là trăn, tất cả chúng ta đều đồng ý với người lớn và tất cả những thứ đó. Bạn không thể đảm bảo rằng sẽ chỉ có một trường hợp duy nhất, nhưng bạn có thể đảm bảo rằng nếu mọi người tạo ra một ví dụ thứ hai, nó sẽ trông xấu xí và nếu họ là người đàng hoàng, coi thường mọi người như một dấu hiệu cảnh báo cho họ. Tôi đang thiếu gì?
Jonas Kölker

7

Kiểm tra câu hỏi Stack Overflow Có cách nào đơn giản, thanh lịch để xác định singletons trong Python không? với một số giải pháp.

Tôi thực sự khuyên bạn nên xem các cuộc nói chuyện của Alex Martelli về các mẫu thiết kế trong python: phần 1phần 2 . Cụ thể, trong phần 1, ông nói về những người độc thân / đối tượng nhà nước chia sẻ.


2
Mặc dù đây không thực sự là một câu trả lời cho câu hỏi của tôi, nhưng các tài nguyên bạn chỉ ra rất hữu ích. Tôi bực bội cho bạn +1
theheadofabroom

3

Đây là cách thực hiện riêng của tôi về singletons. Tất cả bạn phải làm là trang trí lớp học; để có được singleton, sau đó bạn phải sử dụng Instancephương thức. Đây là một ví dụ:

   @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

Và đây là mã:

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
Đó không phải là singleton thực sự. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())nên in Truebằng singleton thật; với mã in của bạnFalse
GingerPlusPlus

@GingerPlusPlus Tôi đã nhận ra một vài hạn chế, nhưng không phải là một trong những hạn chế mà bạn đã chỉ ra. Cảm ơn đã đề cập đến nó. Thật không may, tôi không có thời gian để giải quyết vấn đề này.
Paul Manta

2

Phương thức 3 có vẻ rất gọn gàng, nhưng nếu bạn muốn chương trình của mình chạy trong cả Python 2Python 3 , thì nó không hoạt động. Ngay cả việc bảo vệ các biến thể riêng biệt bằng các thử nghiệm cho phiên bản Python cũng không thành công, vì phiên bản Python 3 có lỗi cú pháp trong Python 2.

Cảm ơn Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclass/ . Nếu bạn muốn chương trình hoạt động trong cả Python 2 và Python 3, bạn cần thực hiện một số thứ như:

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

Tôi cho rằng 'đối tượng' trong bài tập cần được thay thế bằng 'BaseClass', nhưng tôi chưa thử điều đó (tôi đã thử mã như minh họa).


chắc chắn đây không phải là siêu dữ liệu - trong python3 để sử dụng siêu dữ liệu để xây dựng MyClass bạn sẽ làmclass MyClass(metaclass=Singleton)
theheadofabroom

Các mikewatkins.ca liên kết là (hiệu quả) bị phá vỡ.
Peter Mortensen

1

Chà, ngoài việc đồng ý với đề xuất chung của Pythonic về việc có toàn cầu cấp mô-đun, thì sao về điều này:

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)

Đầu ra là:

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

Điểm của _sealedthuộc tính là gì? Theo như tôi thấy điều này không làm gì cả? Một cái gì đó đang gây cười cho tôi về điều này nói rằng nó không hoạt động tốt ... Tôi sẽ chạy một số thử nghiệm so sánh vào cuối tuần này.
theheadofabroom

_seal đảm bảo init của bạn chỉ được chạy một lần; Tôi không thấy bất kỳ điểm nào tại sao nó nên hoạt động kém hơn trình trang trí giống như chức năng thông thường - chức năng chỉ được thực hiện một lần cho mỗi lớp và trả về một lớp kế thừa mới
Guard

BTW, bản chỉnh sửa của bạn chứa các tab phá vỡ các vết lõm Bạn cũng nói 'chúng tôi đang tạo 2 lớp' - bạn có nghĩa là chúng tôi đang tạo ra '1 lớp học thêm'?
Bảo vệ

Có một lớp học thêm là những gì tôi có ý nghĩa. Tôi dự định bao gồm những thứ __init__được gọi mỗi khi nó được khởi tạo. Chỉ đơn giản là 'Được khởi tạo trong class.method'. như để thụt lề - bạn đã sử dụng các tab và dấu cách - Tôi đã sửa hầu hết trong số đó, nhưng dường như đã bỏ lỡ một cái nếu bạn muốn lấy nó (chỉ cần kiểm tra nhật ký chỉnh sửa)
theheadofabroom

tái init : dĩ nhiên đó là tùy thuộc vào bạn, tôi chỉ cố gắng bắt chước các hành vi singleton bằng các ngôn ngữ khác, nơi mã constructor (mà là không chính xác init , nhưng rất gần gũi trong cảm giác của nó) chỉ được gọi một lần nếu bạn muốn init được được gọi mỗi lần, chỉ cần giết tất cả các tham chiếu đến các khoảng trắng / tab được bảo mật - tốt, sau đó emacs của tôi cần sửa chữa. dù sao, ở trên là phiên bản đã sửa
Guard

1

Còn cái này thì sao:

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

Sử dụng nó như một trang trí trên một lớp nên là một singleton. Như thế này:

@singleton
class MySingleton:
    #....

Điều này tương tự như singleton = lambda c: c()trang trí trong một câu trả lời khác. Giống như giải pháp khác, trường hợp duy nhất có tên của lớp ( MySingleton). Tuy nhiên, với giải pháp này, bạn vẫn có thể "tạo" các thể hiện (thực sự lấy ví dụ duy nhất) từ lớp, bằng cách thực hiện MySingleton(). Nó cũng ngăn bạn tạo các thể hiện bổ sung bằng cách thực hiện type(MySingleton)()(điều đó cũng trả về cùng một thể hiện).


Bạn không định nghĩa một lớp để sử dụng nó làm đối tượng.
0xc0de

1
Mỗi lần bạn gọi type(MySingleton)(), MySingleton.__init__()được gọi và đối tượng được khởi tạo nhiều lần; bạn có thể sửa nó bằng văn bản cls.__init__ = lambda self: passcủa bạn singleton. Ngoài ra, ghi đè cls.__call__dường như vô nghĩa và thậm chí có hại - __call__được xác định trong ngữ cảnh này được sử dụng khi bạn gọiMySingleton(any, list, of, arguments) , không phải khi bạn gọi type(MySingleton)(any, list, of, arguments).
GingerPlusPlus

@GingerPlusPlus, Cảm ơn bạn đã chỉ ra rằng __init__()được gọi lại khi làm type(MySingleton)(). Giải pháp bạn đề xuất (thêm cls.__init__ = lambda self: pass) đưa ra lỗi cú pháp, vì phần cuối của biểu thức lambda cần phải là biểu thức, không phải là câu lệnh. Tuy nhiên, thêm cls.__init__ = lambda self: Nonetác phẩm, vì vậy tôi đã thêm nó vào câu trả lời của tôi.
Tolli

1
@GingerPlusPlus, Liên quan đến việc sử dụng __call__. ý định của tôi là làm cho cả hai type(MySingleton)()MySingleton()trả lại ví dụ. Vì vậy, nó đang làm những gì tôi muốn. Bạn có thể nghĩ MySingleton là loại singleton hoặc thể hiện của singleton (hoặc cả hai).
Tolli

1

Tôi sẽ ném tôi vào vòng. Đó là một trang trí đơn giản.

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.

Lợi ích Tôi nghĩ rằng nó có một số giải pháp khác:

  • Nó rõ ràng và súc tích (với mắt tôi; D).
  • Hành động của nó được gói gọn hoàn toàn. Bạn không cần phải thay đổi một điều duy nhất về việc thực hiện YourClass. Điều này bao gồm không cần sử dụng siêu dữ liệu cho lớp của bạn (lưu ý rằng siêu dữ liệu ở trên là ở nhà máy, không phải là lớp "thực").
  • Nó không dựa vào khỉ vá bất cứ thứ gì.
  • Nó minh bạch cho người gọi:
    • Người gọi vẫn chỉ đơn giản là nhập YourClass, nó trông giống như một lớp (vì nó là) và họ sử dụng nó bình thường. Không cần phải thích ứng người gọi với một chức năng nhà máy.
    • Những gì ngay lập tức YourClass()vẫn là một ví dụ thực sự của YourClassbạn đã triển khai, không phải là proxy của bất kỳ loại nào, do đó không có khả năng xảy ra tác dụng phụ do đó.
    • isinstance(instance, YourClass) và các hoạt động tương tự vẫn hoạt động như mong đợi (mặc dù bit này không yêu cầu abc nên loại trừ Python <2.6).

Một nhược điểm xảy ra với tôi: classmethod và staticmethod của lớp thực không thể gọi một cách rõ ràng thông qua lớp nhà máy che giấu nó. Tôi đã sử dụng điều này hiếm khi đủ để tôi không bao giờ gặp phải nhu cầu đó, nhưng nó sẽ dễ dàng được sửa chữa bằng cách sử dụng siêu dữ liệu tùy chỉnh trong nhà máy thực hiện __getattr__()để ủy quyền truy cập thuộc tính all-ish vào lớp thực.

Một mẫu liên quan mà tôi thực sự thấy hữu ích hơn (không phải tôi đang nói những loại điều này được yêu cầu rất thường xuyên) là một mẫu "Duy nhất" trong đó việc tạo ra lớp có cùng các đối số dẫn đến việc lấy lại cùng một thể hiện. Tức là một "singleton per argument". Những điều trên thích nghi tốt với điều này và thậm chí còn ngắn gọn hơn:

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

Tất cả những gì đã nói, tôi đồng ý với lời khuyên chung rằng nếu bạn nghĩ rằng bạn cần một trong những điều này, có lẽ bạn thực sự nên dừng lại một chút và tự hỏi nếu bạn thực sự làm. 99% thời gian, YAGNI.


1
  • Nếu một người muốn có nhiều số lượng của cùng một lớp, nhưng chỉ khi các đối số hoặc kwarg khác nhau, người ta có thể sử dụng điều này
  • Ví dụ.
    1. Nếu bạn có một serialgiao tiếp xử lý lớp và để tạo một cá thể bạn muốn gửi cổng nối tiếp làm đối số, thì với cách tiếp cận truyền thống sẽ không hoạt động
    2. Sử dụng các trình trang trí được đề cập ở trên, người ta có thể tạo nhiều thể hiện của lớp nếu các đối số khác nhau.
    3. Đối với các đối số tương tự, trình trang trí sẽ trả về cùng một thể hiện đã được tạo.
>>> 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

Mã dựa trên câu trả lời của Tolli's .

#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)

Giải trình:

  1. Tạo lớp mới, kế thừa từ đã cho cls
    (nó không sửa đổi clstrong trường hợp ai đó muốn chẳng hạn singleton(list))

  2. Tạo ví dụ. Trước khi ghi đè __new__nó rất dễ dàng.

  3. Bây giờ, khi chúng ta đã dễ dàng tạo cá thể, ghi đè __new__bằng cách sử dụng phương thức được xác định trước đó.
  4. Hàm instancechỉ trả về khi đó là những gì người gọi mong đợi, nếu không sẽ tăng TypeError.
    Điều kiện không được đáp ứng khi ai đó cố gắng kế thừa từ lớp trang trí.

  5. Nếu __new__()trả về một thể hiện của cls, thì __init__()phương thức của thể hiện mới sẽ được gọi như thế __init__(self[, ...]), trong đó bản thân là thể hiện mới và các đối số còn lại giống như được truyền vào __new__().

    instanceđã được khởi tạo, vì vậy chức năng thay thế __init__bằng chức năng không làm gì cả.

Xem nó làm việc trực tuyến


0

Nó hơi giống với câu trả lời của fab nhưng không hoàn toàn giống nhau.

Các hợp đồng singleton không đòi hỏi rằng chúng ta có thể gọi các nhà xây dựng nhiều lần. Là một singleton nên được tạo một lần và một lần duy nhất, không nên nhìn thấy nó được tạo ra chỉ một lần? "Giả mạo" các nhà xây dựng có thể làm suy yếu tính dễ đọc.

Vì vậy, đề nghị của tôi chỉ là thế này:

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()

Điều này không loại trừ việc sử dụng hàm tạo hoặc trường instancetheo mã người dùng:

if Elvis() is King.instance:

... Nếu bạn biết chắc chắn rằng nó Elvischưa được tạo ra, và điều đó Kingcó.

Nhưng nó khuyến khích người dùng sử dụng thephương pháp này một cách phổ biến:

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

Để hoàn thành việc này, bạn cũng có thể ghi đè __delattr__()để tăng Ngoại lệ nếu một nỗ lực được thực hiện để xóa instancevà ghi đè__del__() để nó tăng Ngoại lệ (trừ khi chúng tôi biết chương trình đang kết thúc ...)

Cải tiến hơn nữa


Tôi cảm ơn những người đã giúp đỡ với các bình luận và chỉnh sửa, trong đó nhiều hơn được chào đón. Trong khi tôi sử dụng Jython, nó sẽ hoạt động tốt hơn và an toàn cho chuỗi.

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()

Điểm lưu ý:

  1. Nếu bạn không phân lớp từ đối tượng trong python2.x, bạn sẽ nhận được một lớp kiểu cũ, không sử dụng __new__
  2. Khi trang trí __new__ bạn phải trang trí với @ classmethod hoặc__new__ sẽ là một phương thức không ràng buộc
  3. Điều này có thể có thể được cải thiện bằng cách sử dụng siêu dữ liệu, vì điều này sẽ cho phép bạn tạo themột thuộc tính cấp độ lớp, có thể đổi tên nó thànhinstance

Mặc dù đây là một cách hiểu khác nhau về mẫu đơn, nhưng tôi khá chắc chắn rằng nó vẫn hợp lệ, mặc dù tôi có thể bị cám dỗ sử dụng __new__ hơn là __init__, vì nó hoàn toàn hoạt động trên các thuộc tính lớp và điều này ngăn không cho đó là trường hợp thứ hai. Sự khác biệt sau đó giữa phương thức này và phương thức 2, là việc cố gắng khởi tạo nhiều lần một lần trả về một thể hiện đơn lẻ hay đưa ra một ngoại lệ. Tôi nghĩ rằng tôi rất vui khi thỏa mãn mô hình singleton, một cái dễ sử dụng hơn, trong khi cái kia rõ ràng hơn rằng nó là một singleton.
theheadofabroom

Rõ ràng việc sử dụng tên lớp để __init__ngăn chặn phân lớp, nhưng trong khi điều này làm cho mọi thứ dễ dàng hơn, thì không bắt buộc
theheadofabroom

Cảm ơn ... à vâng, một ví dụ thứ hai nhất thời trước khi Ngoại lệ bị ném. Tôi đã sửa đổi __init__để hy vọng điều này sẽ được phân lớp ...
loài gặm nhấm

the
Thật

Vâng bạn đã đúng. Sau đó, bạn có thể có một singleton lớp phụ SuperElvis và (ví dụ) một singleton lớp phụ ImaginaryElvis ... và chúng có thể cùng tồn tại. Xem thêm suy nghĩ. Xin vui lòng cải thiện mã của tôi.
mike gặm nhấm

0

Một lớp lót (tôi không tự hào, nhưng nó thực hiện công việc):

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

Điều này có thể thực hiện công việc miễn là lớp của bạn chưa được nhập vào bất kỳ mô-đun nào khác ...
Aran-Fey

Thật. Nếu bạn có thể trả chi phí cho việc nhập lớp, bạn có thể chạy Mygroup () ngay sau định nghĩa lớp để tránh điều này.
polvoazul

0

Nếu bạn không cần lười khởi tạo thể hiện của Singleton, thì cách sau đây phải dễ dàng và an toàn cho chuỗi:

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

Cách này Alà một singleton khởi tạo khi nhập mô-đun.


0

Có thể tôi hiểu sai về mẫu đơn nhưng giải pháp của tôi là đơn giản và thực dụng (pythonic?). Mã này hoàn thành hai mục tiêu

  1. Làm cho ví dụ của Footruy cập ở khắp mọi nơi (toàn cầu).
  2. Chỉ có một ví dụ Foocó thể tồn tại.

Đây là mã.

#!/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()

Đầu ra

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

Sau khi vật lộn với điều này một thời gian, cuối cùng tôi đã nghĩ ra cái sau, để đối tượng cấu hình sẽ chỉ được tải một lần, khi được gọi lên từ các mô-đun riêng biệt. Siêu dữ liệu cho phép một cá thể lớp toàn cầu được lưu trữ trong dict dựng sẵn, hiện tại dường như là cách lưu trữ gọn gàng nhất của một chương trình toàn cầu thích hợp.

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

Tôi không thể nhớ nơi tôi tìm thấy giải pháp này, nhưng tôi thấy nó là 'thanh lịch' nhất theo quan điểm không phải là chuyên gia Python của tôi:

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

Tại sao tôi thích điều này? Không có trang trí, không có lớp meta, không có nhiều kế thừa ... và nếu bạn quyết định bạn không muốn nó là Singleton nữa, chỉ cần xóa __new__phương thức. Khi tôi chưa quen với Python (và OOP nói chung), tôi hy vọng ai đó sẽ nói thẳng cho tôi về lý do tại sao đây là một cách tiếp cận khủng khiếp?


2
Tại sao đây là một cách tiếp cận khủng khiếp? Khi bạn muốn tạo một lớp singleton khác, bạn phải sao chép và dán __new__. Đừng lặp lại chính mình .
GingerPlusPlus

Ngoài ra, tại sao mới của bạn mất *args**kwargs, và sau đó không làm gì với họ? Vượt qua chúng dict.__new__theo cách này : dict.__new__(cls, *args, **kwargs).
GingerPlusPlus

Điều này sẽ gọi __init__phương thức mỗi khi lớp được gọi. Nếu __init__phương pháp của bạn thực sự đã làm một cái gì đó, bạn sẽ nhận thấy vấn đề. Bất cứ khi nào bạn làm SomeSingleton(), trạng thái của singleton được đặt lại theo __init__phương thức.
Aran-Fey

-2

Đây là cách ưa thích của tôi để thực hiện singletons:

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
Đây không hoàn toàn là một singleton vì nó cho phép tồn tại nhiều hơn một thể hiện của lớp. Một cải tiến sẽ là làm cho lớp không thể được khởi tạo và có tất cả các phương thức lớp hoạt động trên các thuộc tính của lớp
theheadofabroom

-2

Câu trả lời này có thể không phải là những gì bạn đang tìm kiếm. Tôi muốn một singleton theo nghĩa chỉ có đối tượng đó có danh tính của nó, để so sánh với. Trong trường hợp của tôi, nó đã được sử dụng làm Giá trị Sentinel . Để câu trả lời rất đơn giản, hãy tạo bất kỳ đối tượng nào mything = object()và theo bản chất của python, chỉ có điều đó sẽ có danh tính của nó.

#!python
MyNone = object()  # The singleton

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

Tôi đã học được rằng các mô-đun thực sự có thể được nhập nhiều lần, trong trường hợp như vậy, đây chỉ là một đơn vị địa phương, không thực sự là một đơn vị trong bất kỳ khả năng nào.
ThorSummoner

Bạn có thể giải thích làm thế nào một mô-đun có thể được nhập nhiều lần? Lần duy nhất tôi thấy đó là khi một ngoại lệ xảy ra trong khi mô-đun được tải, người dùng vẫn có thể tải mô-đun sau đó, nhưng các tác dụng phụ sẽ xảy ra rồi, vì vậy một số hành động có thể được thực hiện lần thứ hai.
sleblanc 16/03/19

Khi một mô-đun đã được tải đầy đủ, tôi không thấy cách nào để mô-đun này chạy lại, ngoài việc ép buộc trình thông dịch thực hiện nó bằng cách sử dụng evalhoặc importlib.reload.
sleblanc 16/03/19

-3

Giải pháp này gây ra một số ô nhiễm không gian tên ở cấp mô-đun (ba định nghĩa thay vì chỉ một), nhưng tôi thấy nó dễ thực hiện.

Tôi muốn có thể viết một cái gì đó như thế này (khởi tạo lười biếng), nhưng tiếc là các lớp không có sẵn trong phần định nghĩa của riêng họ.

# 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

Vì điều đó là không thể, chúng ta có thể thoát ra khỏi khởi tạo và thể hiện tĩnh trong

Háo hức khởi tạo:

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

Khởi tạo lười biếng:

Háo hức khởi tạo:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.