Python đưa ra rất nhiều phương thức tích hợp có thể giúp chúng ta ghi đè hầu hết mọi thao tác với các đối tượng. Một trong những phương pháp đó là phương pháp __call__
Nếu chúng ta định nghĩa một phương thức __call__
thì nó cho phép chúng ta sử dụng thể hiện như một hàm
class Demo:
def __init__[self]:
print['instantiated a class']
def __call__[self]:
print['instance is called']
a = Demo[]
# instantiated a class
a[]
# instance is called
Trong ví dụ trên, chúng ta có thể gọi thể hiện của lớp Demo
và sau đó sử dụng nó như một lời gọi hàm
Những lợi thế của việc sử dụng __call__
là gì?
- Rất nhiều thư viện sử dụng phương pháp
__call__
để triển khai giao diện rõ ràng cho API - Sử dụng
__call__
cũng giúp bạn tận dụng tốt nhất cả hai thế giới nơi bạn đang sử dụng lợi thế OOP trong khi sử dụng cú pháp gọi hàm đơn giản - Có một câu trả lời tuyệt vời về StackOverflow được đề cập bên dưới làm nổi bật hầu hết các trường hợp sử dụng
Ví dụ thực tế về phương thức đặc biệt của Python __call__
Tôi biết rằng phương thức __call__ trong một lớp được kích hoạt khi thể hiện của một lớp được gọi. Tuy nhiên, tôi không biết khi nào tôi có thể sử dụng phương thức đặc biệt này, bởi vì người ta có thể chỉ cần tạo một phương thức mới và
Tên phương thức có dấu gạch dưới kép là tiền tố và hậu tố là các phương thức dành riêng cho mục đích sử dụng cụ thể trong Python. Ví dụ: phương thức __init__ được sử dụng cho hàm tạo đối tượng hoặc phương thức __call__ để tạo đối tượng có thể gọi được. Chúng tôi gọi các phương thức này là phương thức dunder, trong đó dunder đề cập đến Double Under [Dấu gạch dưới]. Những phương pháp dunder này còn được gọi là phương pháp ma thuật - Tuy nhiên, không có gì kỳ diệu liên quan đến chúng. Có nhiều nhà phát triển trong cộng đồng Python không thích từ 'ma thuật', vì nó mang lại cảm giác rằng việc sử dụng phương pháp này không được khuyến khích, nhưng thực tế lại hoàn toàn mâu thuẫn.
Hiểu chức năng có thể gọi
Đối tượng trong Python được gọi là có thể gọi được khi đối tượng đó được xác định trong hàm __call__[]. Hàm tương tự có thể được định nghĩa là x[arg 1, arg 2,…], viết tắt của x. __call__[arg1, arg2,…]
Ghi chú. Phương thức callable[] trả về giá trị Boolean cho biết đối tượng có thể gọi được hay không. Hàm này trả về True nếu đối tượng có thể gọi được; . Hơn nữa, cũng có khả năng hàm này có thể trả về True ngay cả khi đối tượng không thể gọi được. Tuy nhiên, nếu phương thức này trả về Sai, thì đối tượng không thể gọi được
Hơn nữa, một lớp Python luôn có thể gọi được. Do đó, chúng ta luôn có thể sử dụng hàm callable[] với một đối tượng của lớp chứ không phải chính lớp đó
Chúng ta hãy xem xét ví dụ sau để hiểu hành vi của hàm callable[] trong Python
Thí dụ
đầu ra
Employee Class is callable = True Employee object is callable = False
Giải trình
Trong ví dụ trên, chúng ta đã định nghĩa một câu lệnh if trong đó nếu đối tượng m có thể gọi được, thì đối tượng đó được gọi dưới dạng một hàm không có đối số, chỉ có đối số, với hàm __call__[], với các đối số thuộc các loại khác nhau và có đối số . Kết quả là các đối tượng cần thiết đã được gọi thành công
Trước hết, bạn không bao giờ cần đến __call__
và đó thường không phải là giải pháp tốt nhất. Tuy nhiên, nó làm cho mã dễ đọc hơn nhiều trong một số ngữ cảnh. Đó là một giải pháp thay thế Pythonic độc đáo cho một số kỹ thuật lập trình chức năng và nó mang lại siêu năng lực cho việc tham số hóa
Mục tiêu của chúng tôi là tạo ra hai chức năng.
Employee Class is callable = True Employee object is callable = False1 và
Employee Class is callable = True Employee object is callable = False2;
# Functional programming style - just using functions, no classesdef send_welcome_message[send, user]:
message = f'Hey, {user.name}. You\'re my buddy.'
send[user, message]# This implementation is pretty easy, no config
def send_via_stdout[user, message]:
print[f'[message to {user.name}]: {message}']def send_welcome_message_via_stdout[user]:
send_welcome_message[send_via_stdout, user]# This one needs a database connection to work
def send_via_db_with_connection[connection, user, message]:
cursor = connection.cursor[]
query = make_insert_message_query[user, message]
cur.execute[query]# Create a send function using a db connection
def make_db_send_fn[conn]:
return lambda u, m: send_via_db_with_connection[conn, u, m]
# Somewhere in our app...conn = db_client.connect["dbname='my_db' user='me' password='pw'"]
send_via_db = make_db_send_fn[conn]def send_welcome_message_via_db[user]:
send_welcome_message[send_via_db, user]# now we can use send_welcome_message_via_db and
# send_welcome_message_via_stdout interchangeably
Đây là một ví dụ mà __call__
sẽ giúp. Có nhiều mức tham số hóa tùy thuộc vào việc chúng ta chọn phiên bản cơ sở dữ liệu hay thiết bị xuất chuẩn. Mã phong cách lập trình chức năng hoàn toàn ổn, nhưng __call__
làm cho nó dễ đọc hơn rất nhiều
# OOP style, with __call__class Sender:Tại sao __call__ dễ đọc hơn?
def send[self, user, message]:
raise NotImplementedError def __call__[self, user, message]:
self.send[user, message]
class StdoutSender[Sender]:
def send[self, user, message]:
print[f'[message to {user.name}]: {message}']
class DatabaseSender[Sender]:
# inject the connection at runtime
def __init__[self, connection]:
self.connection = connection def send[self, user, message]:
cursor = self.connection.cursor[]
query = make_insert_message_query[user, message]
cur.execute[query]
class UserMessageController:
# inject a Sender or a send function
def __init__[self, send]:
self.send = send def send_welcome_message[self, user]:
message = f'Hey, {user.name}. You\'re my buddy.'
self.send[user, message]
send_via_stdout = StdoutSender[]conn = db_client.connect["dbname='my_db' user='me' password='pw'"]
send_via_db = DatabaseSender[conn]stdout_controller = UserMessageController[send_via_stdout]
db_controller = UserMessageController[send_via_db]# Now we can use our controllers interchangeably
# to send welcome messages
Cả phiên bản FP và OOP đều sử dụng bao đóng theo một cách nào đó. Các ứng dụng một phần là đóng cửa. Đối tượng là bao đóng. Tuy nhiên, phiên bản OOP tận dụng cú pháp Python
Employee Class is callable = True Employee object is callable = False5 và
__call__
để làm cho các bao đóng này rõ ràng và thuận tiện hơnChúng ta có thể trao đổi các tham số chức năng để kế thừa và ứng dụng một phần cho các nhà xây dựng. Đó là một sự đánh đổi khôn ngoan trong Python, nhưng không phải trong tất cả các ngôn ngữ. Giải pháp FP có thể tốt hơn cho các ngôn ngữ có kiểu tốt hơn và các tiện ích của FP, như TypeScript. Tuy nhiên, Python có khả năng OOP tốt hơn nhiều và cộng đồng OOP tuyệt vời, vì vậy hãy tận dụng chúng
Khi nào tôi nên sử dụng __call__?Hãy thận trọng khi nghĩ đến việc sử dụng __call__
. Các chức năng hầu như luôn đơn giản hơn và thường dễ dàng hơn. Bạn có nguy cơ làm phức tạp mã của mình khi chọn sử dụng __call__