Perbedaan antara len () dan .__ len __ ()?

100

Apakah ada perbedaan antara menelepon len([1,2,3])atau [1,2,3].__len__()?

Jika tidak ada perbedaan yang terlihat, apa yang dilakukan secara berbeda di belakang layar?

Menandai
sumber

Jawaban:

102

lenmerupakan fungsi untuk mendapatkan panjang suatu koleksi. Ia bekerja dengan memanggil __len__metode objek. __something__atribut khusus dan biasanya lebih dari yang terlihat, dan umumnya tidak boleh dipanggil secara langsung.

Telah diputuskan pada suatu saat yang lalu mendapatkan panjang dari sesuatu harus menjadi fungsi dan bukan kode metode, alasan itu len(a)artinya akan jelas bagi pemula tetapi a.len()tidak akan sejelas. Ketika Python dimulai __len__bahkan tidak ada dan lenmerupakan hal khusus yang bekerja dengan beberapa jenis objek. Apakah situasi ini membuat kita benar-benar masuk akal atau tidak, itu akan tetap ada.

Mike Graham
sumber
66

Seringkali terjadi bahwa perilaku "khas" dari operator atau bawaan adalah memanggil (dengan sintaks yang berbeda dan lebih bagus) metode ajaib yang sesuai (yang memiliki nama seperti __whatever__) pada objek yang terlibat. Seringkali operator atau bawaan memiliki "nilai tambah" (dapat mengambil jalur yang berbeda bergantung pada objek yang terlibat) - dalam kasus lenvs __len__, itu hanya sedikit pemeriksaan kewarasan pada bawaan yang hilang dari metode ajaib:

>>> 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

Saat Anda melihat panggilan ke lenbuilt-in, Anda yakin bahwa, jika program berlanjut setelah itu daripada memunculkan pengecualian, panggilan tersebut mengembalikan integer, non-negatif, dan kurang dari 2 ** 31 - saat Anda melihat panggilan ke xxx.__len__(), Anda tidak memiliki kepastian (kecuali bahwa pembuat kode tidak terbiasa dengan Python atau tidak bagus ;-).

Built-in lainnya memberikan nilai tambah lebih dari sekadar pemeriksaan kewarasan dan keterbacaan. Dengan mendesain secara seragam semua Python untuk bekerja melalui panggilan ke bawaan dan penggunaan operator, tidak pernah melalui panggilan ke metode ajaib, programmer terhindar dari beban mengingat kasus mana yang mana. (Kadang-kadang kesalahan menyelinap di: hingga 2.5, Anda harus memanggil foo.next()- di 2.6, sementara itu masih berfungsi untuk kompatibilitas mundur, Anda harus memanggil next(foo), dan di 3.*, metode ajaib dinamai dengan benar __next__alih-alih "oops-ey" next! - ).

Jadi aturan umumnya adalah jangan pernah memanggil metode ajaib secara langsung (tetapi selalu secara tidak langsung melalui built-in) kecuali Anda tahu persis mengapa Anda perlu melakukan itu (misalnya, ketika Anda mengganti metode seperti itu dalam subkelas, jika subclass harus tunduk pada superclass yang harus dilakukan melalui panggilan eksplisit ke metode ajaib).

Alex Martelli
sumber
Saya adalah pengguna Python pemula (bukan pemrogram pemula yang berpikir) dan saya tidak yakin tentang "Ketika Anda melihat panggilan ke len built-in, Anda yakin bahwa, jika program berlanjut setelah itu daripada memunculkan pengecualian". Saya mencoba ini: def len(x): return "I am a string." print(len(42)) print(len([1,2,3]))dan dicetak I am stringdua kali. Bisakah Anda menjelaskannya lebih lanjut?
Darek Nędza
4
@ DarekNędza Ini tidak ada hubungannya dengan hal di atas, yaitu tentang builtin len. Anda baru saja menentukan fungsi len Anda, yang tentu saja dapat mengembalikan apa pun yang Anda inginkan. OP berbicara tentang builtin len, yang memanggil metode__len__ khusus (bukan fungsi) pada objek yang sedang dipertimbangkan.
Veky
@Veky Bagaimana saya bisa yakin bahwa saya memanggil fungsi built-in lenbukan beberapa fungsi lain (seperti dalam contoh saya) yang kebetulan memiliki nama yang sama - len. Tidak ada peringatan seperti "Anda mendefinisikan ulang fungsi built-in len" atau sesuatu seperti ini. Menurut saya, saya tidak bisa memastikan apa yang dikatakan Alex dalam jawabannya.
Darek Nędza
3
Alex secara eksplisit mengatakan jika Anda menelepon builtin, maka Anda yakin ..._. Dia tidak mengatakan apa-apa tentang memastikan Anda menelepon builtin. Tetapi jika Anda ingin tahu bahwa, Anda dapat: len in vars(__builtins__).values().
Veky
1
Sayangnya, ini adalah contoh lain dari kurangnya kelas dasar yang sama untuk objek dengan Python. Peralihan konteks sintaksis selalu gila. Dalam beberapa kasus, menggunakan metode garis bawah adalah idiom umum, dalam kasus lain seseorang harus menggunakan sesuatu seperti fungsi untuk melakukan sesuatu yang umum ke banyak objek. Ini juga aneh karena banyak objek tidak memiliki penggunaan semantik untuk len. Terkadang model objek lebih seperti C ++, kitchen sinky ..
uchuugaka
28

Anda bisa menganggap len () secara kasar setara dengan

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

Salah satu keuntungannya adalah memungkinkan Anda untuk menulis hal-hal seperti

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

dari pada

map(list.__len__, somelist)

atau

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

Ada perilaku yang sedikit berbeda. Misalnya dalam kasus 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()
John La Rooy
sumber
2
Saya berasumsi maksud Anda, operator.methodcallerbukan operator.attrgetter.
Elazar
5

Anda dapat memeriksa dokumen 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
Dmytro Ozarkiv
sumber