Chủ đề Python an toàn

Cập nhật ngày 22 tháng 12 năm 2022. Ban đầu tôi đã viết bài viết này bằng Python 3. 9. Tôi vừa kiểm tra với phiên bản 3. 11 và mọi thứ hoạt động tốt. Tôi cũng đã thêm một bài kiểm tra đơn vị ví dụ

Trong bài viết này, tôi sẽ chỉ cho bạn cách tạo một lớp Singleton an toàn cho luồng trong Python. Tôi viết bài này vì hầu hết các ví dụ trực tuyến về Singleton trong Python đều rất tệ và/hoặc không an toàn cho luồng. Đây là mã cuối cùng, bởi vì bạn có thể ở đây để tìm câu trả lời nhanh để bạn có thể giải quyết nhiệm vụ hiện tại của mình

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls):
if cls._instance is None:
with cls._lock:
# Another thread could have created the instance
# before we acquired the lock. So check that the
# instance is still nonexistent.
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
Giải thích

Thỏa thuận với phương pháp dunder __new__ của Python là gì?

__new__ được gọi bất cứ khi nào Python khởi tạo một đối tượng mới của một lớp. Thông thường, __new__ đi đến cấp trên của lớp, đó là Object, và khởi tạo một đối tượng mới, đối tượng này sau đó được chuyển đến __init__ với bất kỳ đối số nào được chuyển đến __new__. Chúng tôi chặn phương thức này và yêu cầu nó tạo một và chỉ một thể hiện của lớp (i. e. người độc thân). Đối tượng lớp này sau đó được chuyển đến phương thức __init__ như bình thường

Có chuyện gì với

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
1 vậy?

Đây là một câu hỏi hay.

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
2 là một lớp triển khai các đối tượng khóa nguyên thủy. Nó cho phép luồng đang chạy mã của chúng tôi trở thành luồng duy nhất truy cập mã trong trình quản lý ngữ cảnh của khóa, miễn là nó giữ khóa. Điều này có nghĩa là không có luồng nào khác có thể chạy mã trong khối
class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
3 cùng lúc với luồng có khóa

Có chuyện gì xảy ra với hai tấm séc

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
4?

Chúng tôi kiểm tra xem

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
5 có phải là
class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
6 hay không trước khi lấy khóa. Có một trường hợp cạnh trong đó
class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
5 là
class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
6 trong chủ đề này trong khi một chủ đề khác sắp gọi
class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
9. Trong trường hợp cạnh này, hai đối tượng lớp được tạo, do đó phá vỡ thuộc tính Singleton của lớp chúng ta

Tại sao không bọc toàn bộ nội dung của phương pháp __new__ trong trình quản lý ngữ cảnh

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
1 và tránh kiểm tra
def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()
2 thứ hai, như thế này?

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls, *args, **kwargs):
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance

Điều này sẽ hoạt động và thoạt nhìn có vẻ tốt hơn vì giảm các dòng mã. Tuy nhiên, vấn đề là việc lấy ổ khóa là một hoạt động tốn kém. Có một lớp/phương thức thu thập các khóa khi không cần thiết có thể dẫn đến mã chậm, khó xác định. Chỉ lấy ổ khóa khi cần thiết

Làm thế nào để tôi biết điều này thực sự hoạt động?

Câu hỏi tuyệt vời. Chúng tôi có thể khẳng định hành vi đơn lẻ chính xác thông qua thử nghiệm đơn vị. Ví dụ sau đây là một bài kiểm tra đơn vị được viết bằng pytest

def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()

Cập nhật. Mã này trước đây được sử dụng nếu

def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()
3 thay vì
def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()
4. như vậy

class Singleton:
_instance = None
_lock = threading.Lock()

def __new__(cls):
if not cls._instance: # This is the only difference
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance

Như Paweł Wiszniewski đã chỉ ra trong các nhận xét, bạn có thể gặp phải hành vi lạ nếu Singleton của bạn quá tải

def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()
5. Vì vậy, sẽ an toàn hơn nếu kiểm tra rõ ràng
def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()
6, thay vì dựa vào việc
def test_singleton_is_always_same_object():
assert Singleton() is Singleton()

# Sanity check - a non-singleton class should create two separate
# instances
class NonSingleton:
pass
assert NonSingleton() is not NonSingleton()
7 là giả. Hy vọng rằng bộ thử nghiệm của bạn sẽ phát hiện ra bất kỳ vấn đề nào như vậy từ việc lẻn vào phần mềm của bạn ngay từ đầu

chủ đề gì

An toàn luồng là tránh chạy đua dữ liệu —các tình huống trong đó dữ liệu được đặt thành giá trị đúng hoặc sai, tùy thuộc vào thứ tự trong .

Là chủ đề in Python 3

Hàm print() là một hàm tích hợp để in chuỗi trên thiết bị xuất chuẩn và không an toàn cho chuỗi .