Tại sao Python sử dụng 'phương pháp ma thuật'?


99

Gần đây tôi đã chơi với Python và một điều tôi thấy hơi kỳ lạ là việc sử dụng rộng rãi các 'phương thức ma thuật', ví dụ: để cung cấp độ dài của nó, một đối tượng thực hiện một phương thức def __len__(self), và sau đó nó được gọi là khi bạn viết len(obj).

Tôi chỉ tự hỏi tại sao đối tượng không chỉ cần xác định một len(self)phương pháp và có nó gọi trực tiếp như một thành viên của đối tượng, ví dụ obj.len()? Tôi chắc chắn rằng phải có lý do chính đáng để Python làm theo cách của nó, nhưng là một người mới, tôi vẫn chưa tìm ra chúng là gì.


4
Tôi nghĩ lý do chung là a) lịch sử và b) một cái gì đó giống như len()hoặc reversed()áp dụng cho nhiều loại đối tượng, nhưng một phương pháp như append()chỉ áp dụng cho chuỗi vv
Grant Paul

Câu trả lời:


64

AFAIK, lenđặc biệt về mặt này và có nguồn gốc lịch sử.

Đây là trích dẫn từ Câu hỏi thường gặp :

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 á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 tích hợp 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.

Các "phương pháp kỳ diệu" khác (thực sự được gọi là phương pháp đặc biệt trong dân gian Python) có rất nhiều ý nghĩa và chức năng tương tự tồn tại trong các ngôn ngữ khác. Chúng chủ yếu được sử dụng cho mã được gọi ngầm khi sử dụng cú pháp đặc biệt.

Ví dụ:

  • toán tử quá tải (tồn tại trong C ++ và các toán tử khác)
  • hàm tạo / hủy
  • móc để truy cập các thuộc tính
  • các công cụ để lập trình siêu hình

và như thế...


2
Python và Nguyên tắc ít suy giảm nhất là một bài đọc tốt vì một số lợi thế đối với Python theo cách này (mặc dù tôi thừa nhận rằng tiếng Anh cần hoạt động). Điểm cơ bản: nó cho phép thư viện tiêu chuẩn triển khai hàng tấn mã trở nên rất, rất có thể tái sử dụng nhưng vẫn có thể ghi đè.
jpmc26

20

Từ Zen của Python:

Khi đối mặt với sự mơ hồ, hãy từ chối sự cám dỗ để đoán.
Nên có một-- và tốt nhất là chỉ có một - cách thực hiện điều đó.

Đây là một trong những lý do - với các phương pháp tùy chỉnh, các nhà phát triển sẽ được tự do lựa chọn một tên phương pháp khác nhau, như getLength(), length(), getlength()hoặc bất cứ điều gì. Python thực thi đặt tên nghiêm ngặt để len()có thể sử dụng hàm chung .

Tất cả các hoạt động mà là phổ biến cho nhiều loại đối tượng được đưa vào phương pháp kỳ diệu, giống như __nonzero__, __len__hoặc __repr__. Tuy nhiên, chúng hầu hết là tùy chọn.

Việc nạp chồng toán tử cũng được thực hiện với các phương thức ma thuật (ví dụ __le__), vì vậy cũng hợp lý khi sử dụng chúng cho các phép toán thông thường khác.


Đây là một lập luận thuyết phục. Thỏa mãn hơn là "Guido không thực sự tin vào OO" .... (như tôi đã thấy ở những nơi khác).
Andy Hayden

15

Python sử dụng từ "phương pháp kỳ diệu" , bởi vì những phương pháp đó thực sự thực hiện phép thuật cho bạn lập trình. Một trong những lợi thế lớn nhất của việc sử dụng các phương thức ma thuật của Python là chúng cung cấp một cách đơn giản để làm cho các đối tượng hoạt động giống như các kiểu tích hợp sẵn. Điều đó có nghĩa là bạn có thể tránh những cách xấu xí, phản trực quan và không chuẩn để thực hiện các toán tử cơ bản.

Hãy xem xét một ví dụ sau:

dict1 = {1 : "ABC"}
dict2 = {2 : "EFG"}

dict1 + dict2
Traceback (most recent call last):
  File "python", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Điều này gây ra lỗi, vì loại từ điển không hỗ trợ thêm. Bây giờ, hãy mở rộng lớp từ điển và thêm phương thức ma thuật "__add__" :

class AddableDict(dict):

    def __add__(self, otherObj):
        self.update(otherObj)
        return AddableDict(self)


dict1 = AddableDict({1 : "ABC"})
dict2 = AddableDict({2 : "EFG"})

print (dict1 + dict2)

Bây giờ, nó cho kết quả sau.

{1: 'ABC', 2: 'EFG'}

Do đó, bằng cách thêm phương pháp này, đột nhiên phép thuật đã xảy ra và lỗi bạn gặp phải trước đó đã biến mất.

Tôi hy vọng, nó làm cho mọi thứ rõ ràng với bạn. Để biết thêm thông tin, hãy tham khảo:

Hướng dẫn về các phương pháp kỳ diệu của Python (Rafe Kettler, 2012)


9

Một số hàm này thực hiện nhiều hơn một phương thức đơn lẻ có thể thực hiện (không có phương thức trừu tượng trên lớp cha). Ví dụ, bool()các hành động như thế này:

def bool(obj):
    if hasattr(obj, '__nonzero__'):
        return bool(obj.__nonzero__())
    elif hasattr(obj, '__len__'):
        if obj.__len__():
            return True
        else:
            return False
    return True

Bạn cũng có thể chắc chắn 100% rằng bool() sẽ luôn trả về Đúng hoặc Sai; nếu bạn dựa vào một phương pháp, bạn không thể hoàn toàn chắc chắn mình sẽ nhận lại được gì.

Một số chức năng khác đã tương đối phức tạp triển khai (thêm phức tạp hơn so với các phương pháp kỳ diệu tiềm ẩn có thể sẽ là) là iter()cmp(), và tất cả các phương pháp thuộc tính ( getattr, setattrdelattr). Những thứ như intcũng truy cập các phương pháp ma thuật khi thực hiện cưỡng chế (bạn có thể thực hiện __int__), nhưng làm nhiệm vụ kép như các loại. len(obj)thực sự là một trường hợp mà tôi không tin là nó đã từng khác obj.__len__().


2
Thay vì hasattr()tôi sẽ sử dụng try:/ except AttributeError:và thay vì if obj.__len__(): return True else: return Falsetôi sẽ chỉ nói return obj.__len__() > 0nhưng đó chỉ là những thứ theo phong cách.
Chris Lutz

Trong python 2.6 (mà btw đã tham bool(x)chiếu x.__nonzero__()), phương pháp của bạn sẽ không hoạt động. Các trường hợp bool có một phương thức __nonzero__()và mã của bạn sẽ tiếp tục tự gọi nó khi obj là bool. Có lẽ bool(obj.__bool__())nên đối xử giống như cách bạn đã đối xử __len__? (Hoặc không mã này thực sự làm việc cho Python 3?)
Ponkadoodle

Bản chất vòng tròn của bool () hơi vô lý một cách cố ý, để phản ánh bản chất hình tròn đặc biệt của định nghĩa. Có một lập luận rằng nó đơn giản nên được coi là một nguyên thủy.
Ian Bicking

Sự khác biệt duy nhất (hiện tại) giữa len(x)x.__len__()là cái trước sẽ tăng OverflowError cho độ dài vượt quá sys.maxsize, trong khi cái sau thường không dành cho các loại được triển khai bằng Python. Tuy nhiên, đó là một lỗi hơn là một tính năng (ví dụ: đối tượng phạm vi của Python 3.2 chủ yếu có thể xử lý các phạm vi lớn tùy ý, nhưng việc sử dụng lenvới chúng có thể thất bại. __len__Tuy nhiên, chúng cũng không thành công vì chúng được triển khai bằng C chứ không phải Python)
ncoghlan

4

Chúng không hẳn là những "tên ma thuật". Nó chỉ là giao diện mà một đối tượng phải triển khai để cung cấp một dịch vụ nhất định. Theo nghĩa này, chúng không kỳ diệu hơn bất kỳ định nghĩa giao diện được xác định trước nào mà bạn phải thực hiện lại.


1

Mặc dù lý do chủ yếu là lịch sử, nhưng có một số điểm đặc biệt trong Python len khiến việc sử dụng một hàm thay vì một phương thức là phù hợp.

Một số thao tác bằng Python được thực hiện như phương pháp, ví dụ list.indexdict.append, trong khi những người khác được thực hiện như callables và phương pháp kỳ diệu, ví dụ striterreversed. Hai nhóm đủ khác nhau nên cách tiếp cận khác nhau là hợp lý:

  1. Chúng là chung.
  2. str, int và bạn bè là các loại. Sẽ có ý nghĩa hơn khi gọi hàm tạo.
  3. Việc triển khai khác với lệnh gọi hàm. Ví dụ: itercó thể gọi __getitem__nếu __iter__không có sẵn và hỗ trợ các đối số bổ sung không phù hợp với lệnh gọi phương thức. Vì lý do tương tự it.next()đã được đổi thành next(it)trong các phiên bản Python gần đây - nó có ý nghĩa hơn.
  4. Một số trong số này là họ hàng gần của các nhà khai thác. Có cú pháp để gọi __iter____next__- nó được gọi là forvòng lặp. Để có tính nhất quán, một chức năng sẽ tốt hơn. Và nó làm cho nó tốt hơn cho một số tối ưu nhất định.
  5. Một số chức năng chỉ đơn giản là quá giống với phần còn lại theo một cách nào đó - reprhoạt động giống như strvậy. Có str(x)so với x.repr()sẽ là khó hiểu.
  6. Ví dụ, một số người trong số họ hiếm khi sử dụng phương pháp triển khai thực tế isinstance.
  7. Một số người trong số họ là các nhà khai thác thực tế, getattr(x, 'a')là một cách làm khác x.agetattrchia sẻ nhiều phẩm chất đã nói ở trên.

Cá nhân tôi gọi nhóm đầu tiên giống như phương thức và nhóm thứ hai giống như toán tử. Đó không phải là một sự phân biệt tốt, nhưng tôi hy vọng nó sẽ giúp ích được phần nào.

Có nói điều này, lenkhông hoàn toàn phù hợp với nhóm thứ hai. Nó gần với các thao tác trong lần đầu tiên hơn, với điểm khác biệt duy nhất là nó phổ biến hơn hầu hết các thao tác trong số đó. Nhưng điều duy nhất nó làm là gọi __len__, và nó đang ở rất gần L.index. Tuy nhiên, có một số khác biệt. Ví dụ: __len__có thể được gọi để triển khai các tính năng khác, chẳng hạn như bool, nếu phương thức được gọi, lenbạn có thể phá vỡ bool(x)với lenphương thức tùy chỉnh thực hiện điều hoàn toàn khác.

Tóm lại, bạn có một tập hợp các tính năng rất phổ biến mà các lớp có thể triển khai có thể được truy cập thông qua một toán tử, thông qua một hàm đặc biệt (thường làm nhiều hơn việc triển khai, như một toán tử), trong quá trình xây dựng đối tượng và tất cả chúng có chung một số đặc điểm. Tất cả phần còn lại là một phương pháp. Và lencó phần ngoại lệ đối với quy tắc đó.


0

Không có nhiều thứ để thêm vào hai bài viết trên, nhưng tất cả các chức năng "ma thuật" thực sự không ảo diệu chút nào. Chúng là một phần của mô-đun __ builtins__ được nhập ngầm / tự động khi trình thông dịch khởi động. I E:

from __builtins__ import *

xảy ra mọi lúc trước khi chương trình của bạn bắt đầu.

Tôi luôn nghĩ sẽ đúng hơn nếu Python chỉ làm điều này cho shell tương tác và các tập lệnh yêu cầu để nhập các phần khác nhau từ các nội trang mà chúng cần. Ngoài ra, có thể xử lý __ main__ khác nhau sẽ tốt hơn trong shell so với tương tác. Dù sao, hãy kiểm tra tất cả các chức năng và xem nó sẽ như thế nào nếu không có chúng:

dir (__builtins__)
...
del __builtins__
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.