Tại sao mã Python sử dụng hàm len () thay vì phương thức length?


204

Tôi biết rằng python có một len()hàm được sử dụng để xác định kích thước của chuỗi, nhưng tôi đã tự hỏi tại sao nó không phải là một phương thức của đối tượng chuỗi.

Cập nhật

Ok, tôi nhận ra tôi đã nhầm lẫn đáng xấu hổ. __len__()thực sự là một phương thức của một đối tượng chuỗi. Có vẻ lạ khi thấy mã hướng đối tượng trong Python bằng hàm len trên các đối tượng chuỗi. Hơn nữa, thật kỳ lạ khi xem __len__như tên thay vì chỉ len.

Câu trả lời:


180

Các chuỗi có một phương thức độ dài: __len__()

Giao thức trong Python là triển khai phương thức này trên các đối tượng có độ dài và sử dụng hàm dựng sẵn len(), gọi nó cho bạn, tương tự như cách bạn sẽ thực hiện __iter__()và sử dụng hàm dựng sẵn iter()(hoặc có phương thức được gọi phía sau các cảnh cho bạn) trên các đối tượng có thể lặp lại.

Xem mô phỏng các loại container để biết thêm thông tin.

Đây là một bài đọc tốt về chủ đề của các giao thức trong Python: Python và Nguyên tắc tối thiểu ngạc nhiên


124
Nó làm tôi ngạc nhiên về lý do sử dụng lenlà như thế nào . Họ nghĩ rằng việc buộc mọi người thực hiện sẽ dễ .__len__hơn là bắt mọi người thực hiện .len(). Đó là điều tương tự, và một cái trông sạch sẽ hơn nhiều . Nếu ngôn ngữ sẽ có OOP __len__, thì điều gì trên thế giới là điểm tạo ralen(..)
thay thế vào

38
len, str, v.v. có thể được sử dụng với các hàm bậc cao hơn như map, rút ​​gọn và lọc mà không cần xác định hàm hoặc lambda chỉ để gọi một phương thức. Không phải mọi thứ đều xoay quanh OOP, ngay cả trong Python.
Evicatos

11
Ngoài ra, bằng cách sử dụng một giao thức, họ có thể cung cấp các cách khác để thực hiện mọi thứ. Ví dụ: bạn có thể tạo một lần lặp với __iter__hoặc chỉ với __getitem__iter(x)sẽ hoạt động theo cách nào đó. Bạn có thể tạo một đối tượng bối cảnh có thể sử dụng với __bool__hoặc __len__, và bool(x)sẽ hoạt động theo cách nào đó. Và như thế. Tôi nghĩ Armin giải thích điều này khá hợp lý trong bài đăng được liên kết với nhau nhưng ngay cả khi anh ta không, gọi Python là kẻ ngốc vì một lời giải thích bên ngoài của một anh chàng thường công khai mâu thuẫn với các nhà phát triển cốt lõi sẽ không chính xác công bằng
abarnert

8
@Evicatos: +1 cho "Không phải mọi thứ đều xoay quanh OOP", nhưng tôi sẽ kết thúc bằng " đặc biệt là trong Python", thậm chí không . Python (không giống như Java, Ruby hoặc Smalltalk) không cố gắng trở thành ngôn ngữ "OOP thuần túy"; nó được thiết kế rõ ràng là một ngôn ngữ "đa mô hình". Danh sách hiểu không phải là phương pháp trên iterables. ziplà một hàm cấp cao nhất, không phải là một zip_withphương thức. Và như thế. Chỉ vì mọi thứ là một đối tượng không có nghĩa là trở thành một đối tượng là điều quan trọng nhất đối với mỗi thứ.
abarnert

7
Bài viết đó được viết rất kém, tôi cảm thấy bối rối và tức giận sau khi cố gắng đọc nó. "Trong Python 2.x, loại Tuple chẳng hạn không phơi bày bất kỳ phương thức không đặc biệt nào và bạn có thể sử dụng nó để tạo ra một chuỗi từ đó:" Toàn bộ điều này là sự kết hợp của các câu gần như không thể giải mã được.
MirroredFate

93

Câu trả lời của Jim cho câu hỏi này có thể giúp ích; Tôi sao chép nó ở đây. Trích dẫn Guido van Rossum:

Trước hết, tôi đã chọn len (x) trên x.len () vì lý do HCI (def __len __ () đến sau nhiều). Có hai lý do đan xen thực sự, 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 các phép toán tiền tố - tiền tố (và tiền tố!) có truyền thống lâu dài trong toán học, thích các ký hiệu trong đó các hình ảnh giúp nhà toán học suy nghĩ về một vấn đề. So sánh sự dễ dàng mà chúng ta viết lại một công thức như x * (a + b) thành x a + x b với sự vụng về khi 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ã có ghi len (x) Tôi biết rằng nó đang yêu cầu độ dài của một cái 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 container. Ngược lại, khi tôi đọc x.len (), tôi phải biết rằng x là một loại container thực hiện giao diện hoặc kế thừa từ một lớp có len () chuẩn. Chứng kiến ​​sự nhầm lẫn đôi khi chúng ta gặp phải khi một lớp không triển khai ánh xạ có phương thức get () hoặc key () hoặc thứ gì đó không phải là tệp có phương thức write ().

Nói điều tương tự theo một cách khác, tôi thấy 'len' là một hoạt động tích hợp. Tôi ghét mất nó. / Càng /


37

Có một lenphương pháp:

>>> a = 'a string of some length'
>>> a.__len__()
23
>>> a.__len__
<method-wrapper '__len__' of str object at 0x02005650>

34

Python là một ngôn ngữ lập trình thực dụng, và lý do len()là một chức năng và không phải là một phương pháp str, list, dictvv là thực dụng.

Hàm len()dựng sẵn xử lý trực tiếp các loại tích hợp: triển khai CPython thực len()sự trả về giá trị của ob_sizetrường trong PyVarObjectcấu trúc C đại diện cho bất kỳ đối tượng tích hợp có kích thước thay đổi nào trong bộ nhớ. Đây là nhiều nhanh hơn so với cách gọi một phương pháp - không có tra cứu thuộc tính cần phải xảy ra. Lấy số mục trong một bộ sưu tập là một hoạt động phổ biến và phải làm việc một cách hiệu quả với nhiều loại cơ bản và đa dạng như str, list, array.array, vv

Tuy nhiên, để thúc đẩy tính nhất quán, khi áp dụng len(o)cho loại do người dùng xác định, Python gọi o.__len__()như một dự phòng. __len__, __abs__Và tất cả các phương pháp đặc biệt khác được ghi lại trong Python Data Model làm cho nó dễ dàng để tạo các đối tượng mà cư xử như được xây dựng-in, cho phép các biểu cảm và rất phù hợp API chúng ta gọi là "Pythonic".

Bằng cách triển khai các phương thức đặc biệt, các đối tượng của bạn có thể hỗ trợ phép lặp, quá tải toán tử infix, quản lý bối cảnh trong withcác khối, v.v. Bạn có thể nghĩ Mô hình dữ liệu như một cách sử dụng chính ngôn ngữ Python như một khung mà các đối tượng bạn tạo có thể được tích hợp liền mạch.

Một lý do thứ hai, được hỗ trợ bởi các trích dẫn từ Guido van Rossum như thế này , là nó dễ đọc và viết len(s)hơn s.len().

Ký hiệu len(s)này phù hợp với các toán tử đơn nguyên với ký hiệu tiền tố, như abs(n). len()được sử dụng cách thường xuyên hơn abs(), và nó xứng đáng để dễ viết.

Cũng có thể có một lý do lịch sử: trong ngôn ngữ ABC có trước Python (và có ảnh hưởng rất lớn trong thiết kế của nó), có một toán tử đơn nguyên được viết như #svậy len(s).


12
met% python -c 'import this' | grep 'only one'
There should be one-- and preferably only one --obvious way to do it.

34
Tất nhiên, điều rõ ràng khi làm việc với một đối tượng là một phương thức.
Peter Cooper

4
@Peter: Tôi sẽ trả 20 đô la cho bất kỳ ai có bằng chứng ảnh rằng họ ghi nhận xét của bạn vào lưng của Guido. $ 50 nếu nó trên trán anh ấy.
lỗi

1
Vâng, các nhà thiết kế Python tuân thủ giáo điều nhưng bản thân họ không tôn trọng giáo điều của chính họ.
cắn

2
$ python -c 'nhập cái này' | grep Rõ ràng
Ed Randall

4

Có một số câu trả lời tuyệt vời ở đây, và vì vậy trước khi tôi tự đưa ra, tôi muốn làm nổi bật một vài viên đá quý (không có ý định chơi chữ ruby) tôi đã đọc ở đây.

  • Python không phải là ngôn ngữ OOP thuần túy - đó là mục đích chung, ngôn ngữ đa mô hình cho phép lập trình viên sử dụng mô hình mà họ cảm thấy thoải mái nhất và / hoặc mô hình phù hợp nhất cho giải pháp của họ.
  • Python có các hàm hạng nhất, nên lenthực sự là một đối tượng. Ruby, mặt khác, không có chức năng hạng nhất. Vì vậy, lenđối tượng hàm có các phương thức riêng mà bạn có thể kiểm tra bằng cách chạy dir(len).

Nếu bạn không thích cách thức này hoạt động trong mã của riêng bạn, thì việc bạn triển khai lại các thùng chứa bằng phương pháp ưa thích của bạn là điều không quan trọng (xem ví dụ bên dưới).

>>> class List(list):
...     def len(self):
...         return len(self)
...
>>> class Dict(dict):
...     def len(self):
...         return len(self)
...
>>> class Tuple(tuple):
...     def len(self):
...         return len(self)
...
>>> class Set(set):
...     def len(self):
...         return len(self)
...
>>> my_list = List([1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'])
>>> my_dict = Dict({'key': 'value', 'site': 'stackoverflow'})
>>> my_set = Set({1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'})
>>> my_tuple = Tuple((1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F'))
>>> my_containers = Tuple((my_list, my_dict, my_set, my_tuple))
>>>
>>> for container in my_containers:
...     print container.len()
...
15
2
15
15

0

Bạn cũng có thể nói

>> x = 'test'
>> len(x)
4

Sử dụng Python 2.7.3.


9
lenchức năng đã được đề cập trong câu trả lời trước. Điểm của "câu trả lời" này là gì?
Piotr Dobrogost

0

Một cái gì đó còn thiếu trong phần còn lại của các câu trả lời ở đây: lenhàm kiểm tra xem __len__phương thức trả về không âm int. Thực tế đó lenlà một hàm có nghĩa là các lớp không thể ghi đè hành vi này để tránh kiểm tra. Như vậy, len(obj)cung cấp một mức độ an toàn màobj.len() không thể.

Thí dụ:

>>> class A:
...     def __len__(self):
...         return 'foo'
...
>>> len(A())
Traceback (most recent call last):
  File "<pyshell#8>", line 1, in <module>
    len(A())
TypeError: 'str' object cannot be interpreted as an integer
>>> class B:
...     def __len__(self):
...         return -1
... 
>>> len(B())
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    len(B())
ValueError: __len__() should return >= 0

Tất nhiên, có thể "ghi đè" lenhàm bằng cách gán lại nó là biến toàn cục, nhưng mã thực hiện điều này rõ ràng đáng ngờ hơn nhiều so với mã ghi đè một phương thức trong một lớp.


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.