Sự khác biệt giữa len () và .__ len __ ()?


Câu trả lời:


102

lenlà một hàm để lấy độ dài của một tập hợp. Nó hoạt động bằng cách gọi __len__phương thức của một đối tượng .__something__các thuộc tính đặc biệt và thường hơn là bắt mắt, và thường không nên được gọi trực tiếp.

Nó đã được quyết định vào một thời điểm nào đó từ lâu rằng độ dài của một cái gì đó phải là một hàm chứ không phải mã phương thức, lập luận rằng len(a)ý nghĩa của nó sẽ rõ ràng đối với người mới bắt đầu nhưng a.len()sẽ không rõ ràng như vậy. Khi Python bắt đầu __len__thậm chí còn không tồn tại và lenlà một thứ đặc biệt hoạt động với một vài loại đối tượng. Cho dù tình huống này có khiến chúng ta hoàn toàn hợp lý hay không, thì nó vẫn ở đây.


66

Thường xảy ra trường hợp hành vi "điển hình" của một toán tử hoặc toán tử cài sẵn là gọi (với cú pháp khác và đẹp hơn) các phương thức ma thuật phù hợp (những phương thức có tên như vậy __whatever__) trên các đối tượng liên quan. Thường thì toán tử hoặc toán tử được tích hợp sẵn có "giá trị gia tăng" (nó có thể đi theo các đường dẫn khác nhau tùy thuộc vào các đối tượng liên quan) - trong trường hợp lenvs __len__, nó chỉ là một chút kiểm tra tỉnh táo về cài đặt sẵn bị thiếu trong phương pháp ma thuật:

>>> class bah(object):
...   def __len__(self): return "an inch"
... 
>>> bah().__len__()
'an inch'
>>> len(bah())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object cannot be interpreted as an integer

Khi bạn thấy một lệnh gọi tích lenhợp sẵn, bạn chắc chắn rằng, nếu chương trình tiếp tục sau đó thay vì đưa ra một ngoại lệ, thì lệnh gọi đã trả về một số nguyên, không âm và nhỏ hơn 2 ** 31 - khi bạn thấy một cuộc gọi đếnxxx.__len__() , bạn không có gì chắc chắn (ngoại trừ tác giả của mã hoặc không quen với Python hoặc không tốt ;-).

Các tích hợp sẵn khác thậm chí còn cung cấp nhiều giá trị gia tăng hơn ngoài việc kiểm tra sự tỉnh táo đơn giản và khả năng đọc. Bằng cách thiết kế thống nhất tất cả Python để hoạt động thông qua các cuộc gọi đến nội trang và sử dụng các toán tử, không bao giờ thông qua các lệnh gọi đến các phương thức ma thuật, các lập trình viên không phải nhớ gánh nặng đó là trường hợp nào. (Đôi khi lỗi xảy ra: cho đến 2.5, bạn phải gọi foo.next()- trong 2.6, trong khi điều đó vẫn hoạt động để tương thích ngược, bạn nên gọi next(foo)và trong 3.*, magic method được đặt tên chính xác __next__thay vì "oops-ey"next ! - ).

Vì vậy, quy tắc chung là không bao giờ gọi một phương thức ma thuật trực tiếp (nhưng luôn gián tiếp thông qua một phương thức cài sẵn) trừ khi bạn biết chính xác lý do tại sao bạn cần làm điều đó (ví dụ: khi bạn ghi đè một phương thức như vậy trong một lớp con, nếu lớp con cần chuyển sang lớp cha phải được thực hiện thông qua lệnh gọi rõ ràng tới phương thức ma thuật).


Tôi là người dùng Python mới bắt đầu (không phải lập trình viên mới bắt đầu nghĩ) và tôi không chắc chắn về "Khi bạn thấy một lệnh gọi đến len tích hợp sẵn, bạn chắc chắn rằng, nếu chương trình tiếp tục sau đó thay vì đưa ra một ngoại lệ". Tôi đã thử điều này: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))và nó được in I am stringhai lần. Bạn có thể giải thích thêm được không?
Darek Nędza

4
@ DarekNędza Điều này không liên quan gì đến phần trên, đó là về len nội trang. Bạn vừa xác định hàm len của mình, tất nhiên có thể trả về bất cứ thứ gì bạn muốn. OP đã nói về len nội trang, gọi phương thức__len__ đặc biệt (không phải hàm) trên đối tượng đang được xem xét.
Veky

@Veky Làm sao tôi có thể chắc chắn rằng tôi đang gọi hàm tích hợp lenchứ không phải một số hàm khác (như trong ví dụ của tôi) tình cờ có cùng tên - len. Không có cảnh báo như "Bạn đang xác định lại hàm len được tích hợp sẵn" hoặc đại loại như thế này. Theo tôi, tôi không thể chắc chắn về những gì Alex đã nêu trong câu trả lời của mình.
Darek Nędza

3
Alex nói rõ ràng nếu bạn đang gọi nội trang, thì bạn chắc chắn ..._. Anh ấy không nói gì về việc chắc chắn rằng bạn đang gọi nội trang. Nhưng nếu bạn muốn biết rằng, bạn có thể: len in vars(__builtins__).values().
Veky

1
Đáng buồn thay, đây là một ví dụ khác về việc thiếu một lớp cơ sở chung cho các đối tượng trong Python. Chuyển đổi ngữ cảnh cú pháp luôn luôn là một điều thú vị. Trong một số trường hợp, thành ngữ phổ biến là sử dụng phương thức gạch dưới, trong những trường hợp khác, người ta nên sử dụng một cái gì đó như một hàm để thực hiện một cái gì đó chung cho nhiều đối tượng. Nó cũng kỳ lạ vì nhiều đối tượng không có ngữ nghĩa sử dụng cho len. Đôi khi các mô hình đối tượng là hơn C ++ như thế nào, bếp sinky ..
uchuugaka

28

Bạn có thể coi len () gần tương đương với

def len(x):
    return x.__len__()

Một lợi thế là nó cho phép bạn viết những thứ như

somelist = [[1], [2, 3], [4, 5, 6]]
map(len, somelist) 

thay vì

map(list.__len__, somelist)

hoặc là

map(operator.methodcaller('__len__'), somelist)

Có một chút hành vi khác nhau mặc dù. Ví dụ trong trường hợp ints

>>> (1).__len__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__len__'
>>> len(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()

2
Tôi cho rằng bạn có nghĩa là operator.methodcallerthay vì operator.attrgetter.
Elazar

5

Bạn có thể kiểm tra tài liệu Pythond :

>>> class Meta(type):
...    def __getattribute__(*args):
...       print "Metaclass getattribute invoked"
...       return type.__getattribute__(*args)
...
>>> class C(object):
...     __metaclass__ = Meta
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print "Class getattribute invoked"
...         return object.__getattribute__(*args)
...
>>> c = C()
>>> c.__len__()                 # Explicit lookup via instance
Class getattribute invoked
10
>>> type(c).__len__(c)          # Explicit lookup via type
Metaclass getattribute invoked
10
>>> len(c)                      # Implicit lookup
10
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.