Zen của Python nói rằng chỉ nên có một cách để thực hiện mọi việc - nhưng tôi thường xuyên gặp phải vấn đề quyết định khi nào sử dụng một hàm so với khi nào sử dụng một phương thức.
Hãy lấy một ví dụ tầm thường - một đối tượng ChessBoard. Giả sử chúng ta cần một số cách để có được tất cả các nước đi Vua hợp pháp trên bàn cờ. Chúng ta viết ChessBoard.get_king_moves () hay get_king_moves (flags_board)?
Dưới đây là một số câu hỏi liên quan mà tôi đã xem xét:
- Tại sao trăn sử dụng 'phương pháp ma thuật'?
- Có lý do gì khiến chuỗi Python không có phương thức độ dài chuỗi không?
Các câu trả lời tôi nhận được phần lớn không thể kết luận được:
Tại sao Python sử dụng các phương thức cho một số chức năng (ví dụ: list.index ()) nhưng các hàm cho các chức năng khác (ví dụ: len (danh sách))?
Lý do chính là lịch sử. Các hàm được sử dụng cho những hoạt động chung cho một nhóm kiểu và được dự định để hoạt động ngay cả đối với các đối tượng hoàn toàn không có phương thức (ví dụ: bộ giá trị). Cũng rất tiện lợi khi có một hàm có thể dễ dàng được áp dụng cho một tập hợp các đối tượng vô định hình khi bạn sử dụng các tính năng chức năng của Python (map (), apply () et al).
Trên thực tế, việc triển khai len (), max (), min () dưới dạng một hàm dựng sẵn thực sự là ít mã hơn việc triển khai chúng dưới dạng các phương thức cho từng kiểu. Người ta có thể phân minh về các trường hợp riêng lẻ nhưng đó là một phần của Python và đã quá muộn để thực hiện những thay đổi cơ bản như vậy ngay bây giờ. Các chức năng phải được duy trì để tránh bị hỏng mã lớn.
Tuy thú vị nhưng những điều trên không thực sự nói lên nhiều điều về việc áp dụng chiến lược nào.
Đây là một trong những lý do - với các phương thức tùy chỉnh, các nhà phát triển có thể tự do chọn một tên phương thức khác, như getLength (), length (), getlength () hoặc bất kỳ tên nào khác. Python thực thi đặt tên nghiêm ngặt để có thể sử dụng hàm chung len ().
Thú vị hơn một chút. Ý kiến của tôi là các chức năng theo một nghĩa nào đó, là phiên bản Pythonic của các giao diện.
Cuối cùng, từ chính Guido :
Nói về Khả năng / Giao diện khiến tôi nghĩ về một số tên phương pháp đặc biệt "bất hảo" của chúng tôi. Trong Tham chiếu ngôn ngữ, nó nói, "Một lớp có thể triển khai các hoạt động nhất định được gọi bằng cú pháp đặc biệt (chẳng hạn như các phép toán số học hoặc chỉ số con và cắt) bằng cách xác định các phương thức có tên đặc biệt." Nhưng có tất cả các phương thức này với các tên đặc biệt như
__len__
hoặc__unicode__
dường như được cung cấp vì lợi ích của các hàm tích hợp, chứ không phải để hỗ trợ cú pháp. Có lẽ trong Python dựa trên giao diện, các phương thức này sẽ chuyển thành các phương thức được đặt tên thường xuyên trên ABC, vì vậy điều đó__len__
sẽ trở thànhclass container: ... def len(self): raise NotImplemented
Mặc dù vậy, suy nghĩ về nó một chút nữa, tôi không hiểu tại sao tất cả các hoạt động cú pháp sẽ không chỉ gọi phương thức được đặt tên thông thường thích hợp trên một ABC cụ thể. "
<
", chẳng hạn, có lẽ sẽ gọi "object.lessthan
" (hoặc có thể là "comparable.lessthan
"). Vì vậy, một lợi ích khác sẽ là khả năng cai nghiện Python khỏi cái tên kỳ quặc này, đối với tôi dường như đây là một cải tiến HCI .Hừm. Tôi không chắc mình đồng ý (hình như :-).
Có hai phần về "cơ sở lý luận của Python" mà tôi muốn giải thích trước.
Trước hết, tôi chọn len (x) thay vì x.len () vì lý do HCI (
def __len__()
đến sau này nhiều). Thực ra, có hai lý do đan xen lẫn nhau, cả HCI:(a) Đối với một số phép toán, ký hiệu tiền tố chỉ đọc tốt hơn so với hậu tố - các phép toán tiền tố (và tiền tố!) có một truyền thống lâu đời trong toán học, thích các ký hiệu nơi hình ảnh giúp nhà toán học suy nghĩ về một vấn đề. Hãy so sánh dễ dàng mà chúng tôi viết lại một công thức như
x*(a+b)
vàox*a + x*b
để sự vụng về của làm điều tương tự bằng cách sử dụng ký hiệu OO thô.(b) Khi tôi đọc mã nói rằng
len(x)
tôi biết rằng nó đang yêu cầu độ dài của một thứ gì đó. Điều này cho tôi biết hai điều: kết quả là một số nguyên và đối số là một loại vùng chứa nào đó. Ngược lại, khi tôi đọcx.len()
, tôi phải biết rằng đóx
là một loại vùng chứa nào đó triển khai giao diện hoặc kế thừa từ một lớp có tiêu chuẩnlen()
. Chứng kiến sự nhầm lẫn mà chúng tôi thỉnh thoảng gặp phải khi một lớp không triển khai ánh xạ có mộtget()
hoặckeys()
phương thức, hoặc một cái gì đó không phải là một tệp có mộtwrite()
phương thức.Nói điều tương tự theo cách khác, tôi thấy 'len' là một hoạt động được tích hợp sẵn . Tôi không muốn mất điều đó. Tôi không thể chắc chắn liệu bạn có muốn nói vậy hay không, nhưng 'def len (self): ...' chắc chắn có vẻ như bạn muốn hạ cấp nó xuống một phương thức bình thường. Tôi mạnh mẽ -1 về điều đó.
Cơ sở lý luận thứ hai của Python mà tôi hứa sẽ giải thích là lý do tại sao tôi chọn các phương pháp đặc biệt để xem xét
__special__
chứ không đơn thuầnspecial
. Tôi đã dự đoán rất nhiều hoạt động mà các lớp có thể muốn ghi đè, một số tiêu chuẩn (ví dụ__add__
hoặc__getitem__
), một số không quá tiêu chuẩn (ví dụ: pickle's__reduce__
trong một thời gian dài không hỗ trợ mã C). Tôi không muốn các thao tác đặc biệt này sử dụng các tên phương thức thông thường, vì khi đó các lớp có sẵn hoặc các lớp được viết bởi người dùng không có bộ nhớ bách khoa cho tất cả các phương thức đặc biệt, sẽ có trách nhiệm vô tình xác định các thao tác mà họ không có ý thực hiện , với những hậu quả tai hại có thể xảy ra. Ivan Krstić giải thích điều này ngắn gọn hơn trong thông điệp của anh ấy, gửi đến sau khi tôi viết tất cả những điều này.- --Guido van Rossum (trang chủ: http://www.python.org/~guido/ )
Sự hiểu biết của tôi về điều này là trong một số trường hợp nhất định, ký hiệu tiền tố chỉ có ý nghĩa hơn (tức là Duck.quack có ý nghĩa hơn quack (Vịt) theo quan điểm ngôn ngữ.) Và một lần nữa, các hàm cho phép "giao diện".
Trong trường hợp như vậy, tôi đoán sẽ triển khai get_king_moves chỉ dựa trên điểm đầu tiên của Guido. Nhưng điều đó vẫn để lại rất nhiều câu hỏi mở liên quan đến việc thực hiện một lớp ngăn xếp và hàng đợi với các phương thức push và pop tương tự - chúng nên là hàm hay phương thức? (ở đây tôi sẽ đoán các chức năng, bởi vì tôi thực sự muốn báo hiệu một giao diện push-pop)
TLDR: Ai đó có thể giải thích chiến lược để quyết định khi nào sử dụng các hàm so với các phương pháp không?
X.frob
hayX.__frob__
khôngfrob
.