Đơn Python sử dụng __new__

Năm 1994, mẫu thiết kế Singleton ra đời. Từ Sách GoF, đó là một trong bốn mẫu thiết kế sáng tạo nhằm hạn chế việc khởi tạo một lớp thành một thể hiện duy nhất. Điều thúc đẩy mẫu này là các trường hợp sử dụng như Hệ thống ghi nhật ký hoặc Cơ sở dữ liệu, nơi bạn muốn có một phiên bản điều phối viên duy nhất

Bài viết này là về những gì đối với tôi dường như là cách tiếp cận tốt nhất để triển khai mẫu thiết kế singleton trong python

TLDR. Cách tiếp cận Metaclass dường như là cách tốt nhất trong ba cách, bởi vì nó dễ kiểm tra hơn so với Decorator và tốt hơn nhiều so với Classic về khả năng đọc, khả năng sử dụng lại và khả năng kiểm tra

Singleton là một mẫu thiết kế sáng tạo, đảm bảo rằng chỉ có một đối tượng thuộc loại này tồn tại và cung cấp một điểm truy cập duy nhất cho nó cho bất kỳ mã nào khác

Singleton có những ưu và nhược điểm gần giống như các biến toàn cục. Mặc dù chúng siêu tiện dụng, nhưng chúng phá vỡ tính mô đun của mã của bạn

Bạn không thể chỉ sử dụng một lớp phụ thuộc vào Singleton trong một số ngữ cảnh khác mà không chuyển Singleton sang ngữ cảnh khác. Hầu hết thời gian, giới hạn này xuất hiện trong quá trình tạo các bài kiểm tra đơn vị

Tìm hiểu thêm về Singleton

dẫn đường

giới thiệu

Độc thân ngây thơ

chủ yếu

đầu ra

Singleton an toàn cho luồng

chủ yếu

đầu ra

phức tạp.

Phổ biến.

ví dụ sử dụng. Rất nhiều nhà phát triển coi mẫu Singleton là phản mẫu. Đó là lý do tại sao việc sử dụng mã Python ngày càng giảm

Nhận biết. Singleton có thể được nhận dạng bằng một phương thức tạo tĩnh, phương thức này trả về cùng một đối tượng được lưu trong bộ nhớ cache

Naïve Singleton Singleton an toàn cho luồng

Độc thân ngây thơ

Thật dễ dàng để thực hiện một Singleton cẩu thả. Bạn chỉ cần ẩn hàm tạo và triển khai phương thức tạo tĩnh

Cùng một lớp hoạt động không chính xác trong môi trường đa luồng. Nhiều luồng có thể gọi phương thức tạo đồng thời và nhận một số phiên bản của lớp Singleton

chủ yếu. py. ví dụ khái niệm

class SingletonMeta[type]:
    """
    The Singleton class can be implemented in different ways in Python. Some
    possible methods include: base class, decorator, metaclass. We will use the
    metaclass because it is best suited for this purpose.
    """

    _instances = {}

    def __call__[cls, *args, **kwargs]:
        """
        Possible changes to the value of the `__init__` argument do not affect
        the returned instance.
        """
        if cls not in cls._instances:
            instance = super[].__call__[*args, **kwargs]
            cls._instances[cls] = instance
        return cls._instances[cls]


class Singleton[metaclass=SingletonMeta]:
    def some_business_logic[self]:
        """
        Finally, any singleton should define some business logic, which can be
        executed on its instance.
        """

        # ...


if __name__ == "__main__":
    # The client code.

    s1 = Singleton[]
    s2 = Singleton[]

    if id[s1] == id[s2]:
        print["Singleton works, both variables contain the same instance."]
    else:
        print["Singleton failed, variables contain different instances."]

đầu ra. txt. kết quả thực hiện

Singleton works, both variables contain the same instance.

Singleton an toàn cho luồng

Để khắc phục sự cố, bạn phải đồng bộ hóa các luồng trong lần tạo đối tượng Singleton đầu tiên

Singleton là một mẫu thiết kế đơn giản nhưng gây tranh cãi. Đối với một số lớp chỉ cần một phiên bản của chúng, chẳng hạn như ghi nhật ký, cài đặt, bộ đệm hoặc các đối tượng liên quan đến trình điều khiển thiết bị, việc khởi tạo nhiều phiên bản có thể gây ra nhiều lỗi không mong muốn. Do đó, ý tưởng về singleton, mà…

Xin chào mọi người và chào mừng đến với một bài viết khác về các mẫu thiết kế phần mềm. Trong bài viết này tôi muốn chia sẻ với các bạn thêm một chút về mẫu sáng tạo singleton và cách nó được sử dụng trong phần mềm. Khi chúng tôi xóa phần lý thuyết, chúng tôi sẽ xem xét một số cách để triển khai trong Python. Hãy đi sâu vào

Trong thiết kế phần mềm, mẫu đơn là một mẫu trong đó chỉ một thể hiện duy nhất của một lớp được khởi tạo và sử dụng trong toàn bộ hệ thống được thiết kế. Có một trường hợp duy nhất…

Python đã sử dụng thuật ngữ singleton trước khi “Mẫu Singleton” được định nghĩa bởi cộng đồng mẫu thiết kế hướng đối tượng. Vì vậy, chúng ta nên bắt đầu bằng cách phân biệt một số ý nghĩa của “singleton” trong Python

  1. Một bộ có độ dài một được gọi là một singleton. Mặc dù định nghĩa này có thể khiến một số lập trình viên ngạc nhiên, nhưng nó phản ánh định nghĩa ban đầu về đơn tử trong toán học. một tập hợp chứa chính xác một phần tử. Bản thân Hướng dẫn Python đã giới thiệu cho những người mới làm quen với định nghĩa này khi chương về Cấu trúc dữ liệu của nó gọi bộ một phần tử là “singleton” và từ này tiếp tục được sử dụng theo nghĩa đó trong phần còn lại của tài liệu Python. Khi hướng dẫn về Mở rộng và Nhúng cho biết, “Để gọi hàm Python… với một đối số, hãy chuyển một bộ dữ liệu đơn”, điều đó có nghĩa là một bộ dữ liệu chứa chính xác một mục
  2. Các mô-đun là "đơn lẻ" trong Python vì
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    7 chỉ tạo một bản sao duy nhất của mỗi mô-đun; . Ví dụ: khi chương Đối tượng mô-đun của Sách hướng dẫn tham khảo API Python/C khẳng định rằng “Khởi tạo một pha tạo ra các mô-đun đơn”, điều đó có nghĩa là “mô-đun đơn” là mô-đun chỉ có một đối tượng được tạo.
  3. Một “singleton” là một thể hiện của lớp đã được gán một tên chung thông qua Mẫu đối tượng toàn cầu . Ví dụ: Câu hỏi thường gặp về lập trình Python chính thức trả lời câu hỏi “Làm cách nào để chia sẻ các biến toàn cục giữa các mô-đun?” .
  4. Các đối tượng flyweight riêng lẻ là ví dụ về Mẫu Flyweight thường được các lập trình viên Python gọi là các đối tượng "singleton". Ví dụ: một nhận xét bên trong
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    9 của Thư viện chuẩn khẳng định rằng “Bộ dữ liệu trống của CPython là một bộ đơn lẻ” — nghĩa là trình thông dịch Python chỉ tạo một đối tượng bộ dữ liệu trống duy nhất, mà bộ dữ liệu này sẽ trả về lặp lại mỗi khi nó vượt qua độ dài bằng 0 . Một nhận xét trong
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    1 đề cập tương tự đến "bộ đơn đóng băng trống. ” Nhưng cả hai đối tượng đơn lẻ này đều không phải là một ví dụ về Mẫu Singleton của Gang of Four, bởi vì không đối tượng nào là thể hiện duy nhất của lớp của nó.
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    2 cho phép bạn xây dựng các bộ dữ liệu khác bên cạnh bộ dữ liệu trống và
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    3 cho phép bạn xây dựng các bộ cố định khác. Tương tự, các đối tượng
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    4 và
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    5 là một cặp đối tượng bay, không phải là ví dụ về Mẫu Singleton, bởi vì không phải là ví dụ duy nhất của
    # Object creation in a language
    # that has a “new” keyword.
    
    log = new Logger[]
    
    6.
  5. Cuối cùng, các lập trình viên Python trong một số trường hợp hiếm hoi thực sự có nghĩa là “Mẫu Singleton” khi họ gọi một đối tượng là “singleton”. đối tượng duy nhất được trả về bởi lớp của nó mỗi khi lớp được gọi

Thư viện chuẩn Python 2 không bao gồm ví dụ về Mẫu Singleton. Mặc dù nó có các đối tượng đơn lẻ như

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
7 và
# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
8, nhưng ngôn ngữ này đã cung cấp quyền truy cập vào chúng thông qua Mẫu đối tượng toàn cầu Pythonic hơn bằng cách đặt tên cho chúng trong mô-đun
# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
9. Nhưng các lớp học của họ không thể gọi được.

Singleton works, both variables contain the same instance.
5

Tuy nhiên, trong Python 3, các lớp đã được nâng cấp để sử dụng Mẫu Singleton

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis

Điều này làm cho cuộc sống của các lập trình viên dễ dàng hơn, những người cần một lệnh gọi nhanh luôn trả về

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
7, mặc dù những trường hợp như vậy rất hiếm. Trong hầu hết các dự án Python, các lớp này không bao giờ được gọi và lợi ích vẫn hoàn toàn là lý thuyết. Khi các lập trình viên Python cần đối tượng
# Object creation in a language
# that has a “new” keyword.

log = new Logger[]
7, họ sử dụng Mẫu đối tượng toàn cầu và chỉ cần nhập tên của nó.

Quá trình triển khai của Băng nhóm 4 người¶

Ngôn ngữ C++ mà Gang of Four đang nhắm đến đã áp đặt một cú pháp riêng biệt cho việc tạo đối tượng, trông giống như

# Object creation in a language
# that has a “new” keyword.

log = new Logger[]

Một dòng C++ có nội dung

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
02 luôn tạo một thể hiện lớp mới — nó không bao giờ trả về một singleton. Với sự hiện diện của cú pháp đặc biệt này, các tùy chọn của họ để cung cấp các đối tượng đơn lẻ là gì?

  1. Bang of Four không dễ dàng sử dụng Mẫu đối tượng toàn cầu vì nó không hoạt động đặc biệt hiệu quả trong các phiên bản đầu tiên . Ở đó, tất cả các tên toàn cầu đều chia sẻ một không gian tên toàn cầu đông đúc duy nhất, vì vậy các quy ước đặt tên phức tạp là cần thiết để ngăn các tên từ các thư viện khác nhau xung đột. The Gang đánh giá rằng việc thêm cả một lớp và thể hiện đơn lẻ của nó vào không gian tên toàn cầu đông đúc sẽ là quá mức. Và vì các lập trình viên C++ không thể kiểm soát thứ tự khởi tạo các đối tượng toàn cầu, nên không có đối tượng toàn cục nào có thể phụ thuộc vào việc có thể gọi bất kỳ đối tượng nào khác, do đó, trách nhiệm khởi tạo các đối tượng toàn cầu thường thuộc về mã máy khách.
  2. Không có cách nào để ghi đè ý nghĩa của
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    02 trong C++, vì vậy cần có một cú pháp thay thế nếu tất cả các máy khách đều nhận cùng một đối tượng. Tuy nhiên, ít nhất có thể tạo ra lỗi thời gian biên dịch cho mã máy khách khi gọi
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    02 bằng cách đánh dấu hàm tạo của lớp là
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    05 hoặc
    >>> NoneType = type[None]
    >>> print[NoneType[]]
    None
    >>> type[Ellipsis][]
    Ellipsis
    
    06
  3. Vì vậy, Gang of Four xoay quanh một phương thức lớp sẽ trả về đối tượng đơn lẻ của lớp. Không giống như một hàm toàn cục, một phương thức lớp tránh thêm một tên khác vào không gian tên toàn cầu và không giống như một phương thức tĩnh, nó cũng có thể hỗ trợ các lớp con là các lớp đơn

Làm thế nào mã Python có thể minh họa cách tiếp cận của họ? . Một cách khác là đưa ra một ngoại lệ trong

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
80 để không thể khởi tạo đối tượng bình thường. Sau đó, phương thức lớp có thể sử dụng thủ thuật phương thức dunder để tạo đối tượng mà không kích hoạt ngoại lệ

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
0

Điều này thành công ngăn khách hàng tạo các phiên bản mới bằng cách gọi lớp

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
8

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
1

Thay vào đó, người gọi được hướng dẫn sử dụng phương thức lớp

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
81, phương thức này tạo và trả về một đối tượng

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
3

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
4

Các cuộc gọi tiếp theo tới

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
81 trả về singleton mà không lặp lại bước khởi tạo [như chúng ta có thể thấy từ thực tế là “Tạo phiên bản mới” không được in lại], chính xác như dự định của Nhóm Bốn Người

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
7

Có nhiều sơ đồ phức tạp hơn mà tôi có thể tưởng tượng để triển khai phương thức lớp Gang of Four ban đầu trong Python, nhưng tôi nghĩ ví dụ trên thực hiện tốt nhất việc minh họa sơ đồ ban đầu với ít phép thuật nhất có thể. Vì mẫu của Gang of Four dù sao cũng không phù hợp với Python, nên tôi sẽ chống lại sự cám dỗ để lặp lại nó nhiều hơn và thay vào đó chuyển sang cách mẫu được hỗ trợ tốt nhất trong Python

Một triển khai Pythonic hơn¶

Theo một nghĩa nào đó, Python bắt đầu được chuẩn bị tốt hơn C++ cho Mẫu Singleton, bởi vì Python thiếu từ khóa

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
02 buộc một đối tượng mới được tạo. Thay vào đó, các đối tượng được tạo bằng cách gọi một hàm có thể gọi được, điều này không áp đặt giới hạn cú pháp đối với hoạt động mà hàm có thể gọi được thực sự thực hiện.

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
8

Để cho phép tác giả kiểm soát các cuộc gọi đến một lớp, Python 2. 4 đã thêm phương thức

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6 dunder để hỗ trợ các mẫu sáng tạo thay thế như Mẫu Singleton và Mẫu Flyweight .

Web có đầy đủ các công thức Mẫu Singleton có tính năng

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6, mỗi công thức đề xuất một cơ chế phức tạp hơn hoặc ít phức tạp hơn để giải quyết vấn đề lớn nhất của phương pháp. thực tế là
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
80 luôn được gọi trên giá trị trả về, cho dù đối tượng được trả về là mới hay không. Để làm cho ví dụ của riêng tôi trở nên đơn giản, tôi sẽ không định nghĩa một phương thức
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
80 và do đó tránh phải làm việc xung quanh nó

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
1

Đối tượng được tạo trong lần gọi đầu tiên đến lớp

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
2

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
3

Nhưng cuộc gọi thứ hai trả về cùng một trường hợp. Thông báo “Tạo đối tượng” không in ra, đối tượng khác cũng không được trả về

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
4

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
5

Ví dụ trên chọn sự đơn giản, với chi phí thực hiện tra cứu thuộc tính

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
88 hai lần trong trường hợp phổ biến. Đối với những lập trình viên ngại lãng phí như vậy, kết quả tất nhiên có thể được gán tên và sử dụng lại trong câu lệnh return. Và nhiều cải tiến khác có thể được tưởng tượng sẽ dẫn đến mã byte nhanh hơn. Nhưng dù được điều chỉnh công phu đến đâu, mẫu trên là cơ sở của mọi lớp Python ẩn một đối tượng đơn lẻ đằng sau những gì đọc giống như khởi tạo lớp bình thường

Bản án¶

Mặc dù Mẫu Singleton ban đầu của Gang of Four không phù hợp với một ngôn ngữ như Python thiếu các khái niệm về

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
02,
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
06 và
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
05, nhưng không dễ để loại bỏ mẫu khi nó được xây dựng trên đỉnh
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6 — xét cho cùng, các singleton là một phần của

Nhưng Mẫu Singleton trong Python có một số nhược điểm

Phản đối đầu tiên là việc triển khai Mẫu Singleton khiến nhiều lập trình viên Python khó đọc. Cách thay thế Mẫu đối tượng chung dễ đọc. nó chỉ đơn giản là câu lệnh gán quen thuộc, được đặt ở cấp cao nhất của mô-đun. Nhưng một lập trình viên Python lần đầu tiên đọc phương thức

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6 có lẽ sẽ phải dừng lại và tìm kiếm tài liệu để hiểu chuyện gì đang xảy ra.

Phản đối thứ hai là Mẫu Singleton thực hiện các cuộc gọi đến lớp, như

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
15, gây hiểu lầm cho người đọc. Trừ khi nhà thiết kế đã đặt “Singleton” hoặc một số gợi ý khác trong tên lớp và người đọc biết các mẫu thiết kế đủ rõ để hiểu gợi ý, mã sẽ đọc như thể một phiên bản mới đang được tạo và trả về

Phản đối thứ ba là Mẫu Singleton buộc phải cam kết thiết kế mà Mẫu Đối tượng Toàn cầu thì không. Việc cung cấp một đối tượng toàn cầu vẫn giúp lập trình viên tự do tạo các phiên bản khác của lớp — điều này có thể đặc biệt hữu ích cho các thử nghiệm, cho phép mỗi người kiểm tra một đối tượng hoàn toàn riêng biệt mà không cần đặt lại đối tượng dùng chung trở lại trạng thái tốt đã biết. Nhưng Mẫu Singleton làm cho các trường hợp bổ sung không thể. [Trừ khi người gọi sẵn sàng khom lưng để vá khỉ; hoặc tạm thời sửa đổi

>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
16 để phá vỡ logic trong
>>> NoneType = type[None]
>>> print[NoneType[]]
None
>>> type[Ellipsis][]
Ellipsis
6; hoặc tạo một lớp con thay thế phương thức. Nhưng một khuôn mẫu bạn phải giải quyết nói chung là một khuôn mẫu bạn nên tránh. ]

Vậy tại sao bạn lại sử dụng Mẫu Singleton trong Python?

Một tình huống thực sự yêu cầu mẫu sẽ là một lớp hiện có, do yêu cầu mới, giờ đây sẽ hoạt động tốt nhất như một thể hiện duy nhất. Nếu không thể di chuyển tất cả mã máy khách để ngừng gọi trực tiếp lớp và bắt đầu sử dụng một đối tượng toàn cầu, thì Mẫu Singeton sẽ là một cách tiếp cận tự nhiên để xoay vòng sang một mã đơn trong khi vẫn giữ nguyên cú pháp cũ

Việc sử dụng __ mới __ trong Python là gì?

Theo tài liệu chính thức của Python, __new__ được sử dụng khi bạn cần kiểm soát việc tạo phiên bản mới trong khi __init__ được sử dụng để kiểm soát .

__ mới __ có phải là một phương thức lớp không?

Trong đối tượng lớp cơ sở, phương thức __new__ được định nghĩa là phương thức tĩnh yêu cầu truyền tham số cls. cls đại diện cho lớp cần được khởi tạo và trình biên dịch sẽ tự động cung cấp tham số này tại thời điểm khởi tạo.

Bạn có thể khởi tạo một singleton không?

Chúng ta có thể phân biệt một lớp Singleton với các lớp thông thường về quá trình khởi tạo đối tượng của lớp. Để khởi tạo một lớp bình thường, chúng ta sử dụng hàm tạo java. Mặt khác, để khởi tạo một lớp đơn lẻ, chúng ta sử dụng phương thức getInstance[] .

Chúng ta có thể tạo 2 đối tượng của lớp singleton không?

Mục đích của Singleton là kiểm soát việc tạo đối tượng, giới hạn số lượng thành một nhưng cho phép linh hoạt tạo nhiều đối tượng hơn nếu tình huống thay đổi. Vì chỉ có một phiên bản Singleton nên mọi trường phiên bản của Singleton sẽ chỉ xuất hiện một lần cho mỗi lớp , giống như trường tĩnh.

Chủ Đề