Mengapa Python menggunakan 'metode ajaib'?


99

Saya telah bermain-main dengan Python baru-baru ini, dan satu hal yang menurut saya agak aneh adalah penggunaan ekstensif 'metode ajaib', misalnya untuk membuat panjangnya tersedia, sebuah objek menerapkan metode def __len__(self), dan kemudian dipanggil ketika Anda menulis len(obj).

Saya hanya bertanya-tanya mengapa objek tidak hanya mendefinisikan len(self)metode dan memanggilnya langsung sebagai anggota objek, misalnya obj.len()? Saya yakin pasti ada alasan bagus untuk Python melakukan seperti itu, tetapi sebagai pemula saya belum mengetahui apa itu.


4
Saya pikir alasan umumnya adalah a) historis dan b) sesuatu seperti len()atau reversed()berlaku untuk banyak jenis objek, tetapi metode seperti append()hanya berlaku untuk urutan, dll.
Grant Paul

Jawaban:


64

AFAIK, lenistimewa dalam hal ini dan memiliki akar sejarah.

Berikut kutipan dari FAQ :

Mengapa Python menggunakan metode untuk beberapa fungsionalitas (misalnya list.index ()) tetapi fungsi untuk yang lain (misalnya len (list))?

Alasan utamanya adalah sejarah. Fungsi digunakan untuk operasi yang umum untuk sekelompok tipe dan yang dimaksudkan untuk bekerja bahkan untuk objek yang tidak memiliki metode sama sekali (misalnya tupel). Juga nyaman untuk memiliki fungsi yang dapat dengan mudah diterapkan ke kumpulan objek yang tidak berbentuk saat Anda menggunakan fitur fungsional Python (map (), apply () et al).

Faktanya, mengimplementasikan len (), max (), min () sebagai fungsi bawaan sebenarnya lebih sedikit kode daripada mengimplementasikannya sebagai metode untuk setiap jenis. Seseorang dapat berdalih tentang kasus individu tetapi itu adalah bagian dari Python, dan sudah terlambat untuk membuat perubahan mendasar seperti itu sekarang. Fungsinya harus tetap ada untuk menghindari kerusakan kode besar-besaran.

"Metode magis" lainnya (sebenarnya disebut metode khusus dalam cerita rakyat Python) sangat masuk akal, dan fungsi serupa ada di bahasa lain. Mereka kebanyakan digunakan untuk kode yang dipanggil secara implisit ketika sintaks khusus digunakan.

Sebagai contoh:

  • operator yang kelebihan beban (ada di C ++ dan lainnya)
  • konstruktor / destruktor
  • kait untuk mengakses atribut
  • alat untuk metaprogramming

dan seterusnya...


2
Python and the Principle of Least Astonishment adalah bacaan yang baik untuk beberapa keuntungan Python dengan cara ini (meskipun saya akui bahasa Inggris perlu diperbaiki). Poin dasarnya: ini memungkinkan pustaka standar untuk mengimplementasikan banyak kode yang menjadi sangat, sangat dapat digunakan kembali tetapi masih dapat diganti.
jpmc26

20

Dari Zen of Python:

Dalam menghadapi ambiguitas, tolak godaan untuk menebak.
Seharusnya ada satu - dan sebaiknya hanya satu --cara yang jelas untuk melakukannya.

Ini adalah salah satu alasan - dengan metode kustom, pengembang akan bebas untuk memilih nama metode yang berbeda, seperti getLength(), length(), getlength()atau apapun. Python memberlakukan penamaan yang ketat sehingga fungsi umum len()dapat digunakan.

Semua operasi yang umum untuk banyak jenis objek dimasukkan ke dalam metode ajaib, seperti __nonzero__, __len__atau __repr__. Namun, sebagian besar bersifat opsional.

Overloading operator juga dilakukan dengan metode ajaib (misalnya __le__), jadi masuk akal untuk menggunakannya juga untuk operasi umum lainnya.


Ini adalah argumen yang meyakinkan. Lebih memuaskan bahwa "Guido tidak benar-benar percaya pada OO" .... (seperti yang saya klaim di tempat lain).
Andy Hayden

15

Python menggunakan kata "metode ajaib" , karena metode tersebut benar-benar melakukan sihir untuk program Anda. Salah satu keuntungan terbesar menggunakan metode sihir Python adalah bahwa metode ini menyediakan cara sederhana untuk membuat objek berperilaku seperti tipe bawaan. Itu berarti Anda dapat menghindari cara-cara yang buruk, kontra-intuitif, dan tidak standar dalam melakukan operator dasar.

Pertimbangkan contoh berikut:

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'

Ini memberikan kesalahan, karena jenis kamus tidak mendukung penambahan. Sekarang, mari perluas kelas kamus dan tambahkan metode ajaib "__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)

Sekarang, ini memberikan hasil sebagai berikut.

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

Jadi, dengan menambahkan metode ini, tiba-tiba keajaiban telah terjadi dan kesalahan yang Anda dapatkan sebelumnya, telah hilang.

Saya harap, ini membuat semuanya jelas bagi Anda. Untuk informasi lebih lanjut, lihat:

Panduan Metode Sulap Python (Rafe Kettler, 2012)


9

Beberapa fungsi ini melakukan lebih dari satu metode yang dapat diimplementasikan (tanpa metode abstrak pada superclass). Misalnya bool()tindakan seperti ini:

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

Anda juga bisa 100% yakin bahwa bool()akan selalu mengembalikan True atau False; jika Anda mengandalkan suatu metode, Anda tidak bisa sepenuhnya yakin apa yang akan Anda dapatkan.

Beberapa fungsi lain yang memiliki implementasi yang relatif rumit (lebih rumit daripada metode sihir yang mendasari kemungkinan besar) adalah iter()dan cmp(), dan semua metode atribut ( getattr, setattrdan delattr). Hal-hal seperti intjuga mengakses metode sihir saat melakukan pemaksaan (Anda dapat menerapkan __int__), tetapi melakukan tugas ganda sebagai tipe. len(obj)sebenarnya adalah satu kasus yang menurut saya tidak pernah berbeda obj.__len__().


2
Alih-alih hasattr()saya akan menggunakan try:/ except AttributeError:dan alih-alih if obj.__len__(): return True else: return Falsesaya hanya akan mengatakan return obj.__len__() > 0tetapi itu hanya hal-hal gaya.
Chris Lutz

Di python 2.6 (yang bool(x)dirujuk btw x.__nonzero__()), metode Anda tidak akan berfungsi. Instance bool memiliki metode __nonzero__(), dan kode Anda akan terus memanggil dirinya sendiri begitu obj menjadi bool. Mungkin bool(obj.__bool__())harus diperlakukan sama dengan Anda __len__? (Atau apakah kode ini benar-benar berfungsi untuk Python 3?)
Ponkadoodle

Sifat melingkar dari bool () agak sengaja tidak masuk akal, untuk mencerminkan sifat khusus melingkar dari definisi tersebut. Ada argumen bahwa itu seharusnya dianggap primitif.
Ian Bicking

Satu-satunya perbedaan (saat ini) antara len(x)dan x.__len__()adalah bahwa yang pertama akan memunculkan OverflowError untuk panjang yang melebihi sys.maxsize, sedangkan yang terakhir umumnya tidak untuk jenis yang diimplementasikan dengan Python. Itu lebih merupakan bug daripada fitur, meskipun (misalnya objek jangkauan Python 3.2 sebagian besar dapat menangani rentang besar yang sewenang-wenang, tetapi menggunakannya lendengan mereka mungkin gagal. Namun __len__gagal juga, karena mereka diterapkan di C daripada Python)
ncoghlan

4

Mereka sebenarnya bukan "nama ajaib". Ini hanyalah antarmuka yang harus diterapkan objek untuk menyediakan layanan tertentu. Dalam pengertian ini, mereka tidak lebih ajaib daripada definisi antarmuka yang telah ditentukan sebelumnya yang harus Anda implementasikan ulang.


1

Meskipun alasannya sebagian besar bersifat historis, ada beberapa keanehan dalam Python lenyang menggunakan fungsi alih-alih metode yang sesuai.

Beberapa operasi di Python diimplementasikan sebagai metode, misalnya list.indexdan dict.append, sementara yang lain diimplementasikan sebagai callable dan metode ajaib, misalnya strdaniter dan reversed. Kedua kelompok tersebut cukup berbeda sehingga pendekatan yang berbeda dapat dibenarkan:

  1. Mereka biasa.
  2. str, int dan teman adalah tipe. Lebih masuk akal untuk memanggil konstruktor.
  3. Implementasinya berbeda dari pemanggilan fungsi. Misalnya, itermungkin memanggil __getitem__jika __iter__tidak tersedia, dan mendukung argumen tambahan yang tidak cocok dalam panggilan metode. Untuk alasan yang samait.next() telah diubah menjadinext(it) dalam versi terbaru Python - itu lebih masuk akal.
  4. Beberapa di antaranya adalah kerabat dekat operator. Ada sintaks untuk memanggil __iter__dan __next__- itu disebutfor loop. Untuk konsistensi, suatu fungsi lebih baik. Dan itu membuatnya lebih baik untuk pengoptimalan tertentu.
  5. Beberapa fungsi terlalu mirip dengan yang lain dalam beberapa hal - reprbertindak seperti stritu. Memiliki str(x)versusx.repr() akan membingungkan.
  6. Beberapa dari mereka jarang menggunakan metode implementasi yang sebenarnya, misalnya isinstance .
  7. Beberapa dari mereka adalah operator yang sebenarnya, getattr(x, 'a')merupakan cara lain untuk melakukan x.adan getattrberbagi banyak kualitas yang disebutkan di atas.

Saya pribadi menyebut metode grup pertama dan grup kedua seperti operator. Ini bukan perbedaan yang sangat bagus, tapi saya harap ini bisa membantu.

Karena itu, lentidak sepenuhnya cocok dengan kelompok kedua. Ini lebih dekat dengan operasi di yang pertama, dengan satu-satunya perbedaan bahwa itu jauh lebih umum daripada hampir semua dari mereka. Tapi satu-satunya hal yang dilakukannya adalah menelepon __len__, dan itu sangat dekat L.index. Namun, ada beberapa perbedaan. Misalnya, __len__mungkin dipanggil untuk implementasi fitur lain, seperti bool, jika metode dipanggil, lenAnda mungkin memutuskan bool(x)dengan kustomlen metode yang melakukan hal yang sangat berbeda.

Singkatnya, Anda memiliki sekumpulan fitur yang sangat umum yang mungkin diterapkan kelas yang dapat diakses melalui operator, melalui fungsi khusus (yang biasanya melakukan lebih dari sekadar implementasi, seperti yang dilakukan operator), selama konstruksi objek, dan semuanya berbagi beberapa ciri umum. Sisanya adalah metode. Dan lenmerupakan pengecualian dari aturan itu.


0

Tidak banyak yang bisa ditambahkan ke dua tulisan di atas, tapi semua fungsi "ajaib" sebenarnya tidak ajaib sama sekali. Mereka adalah bagian dari modul __ builtins__ yang secara implisit / otomatis diimpor ketika interpreter dimulai. Yaitu:

from __builtins__ import *

terjadi setiap kali sebelum program Anda dimulai.

Saya selalu berpikir akan lebih benar jika Python hanya melakukan ini untuk shell interaktif, dan membutuhkan skrip untuk mengimpor berbagai bagian dari bawaan yang mereka butuhkan. Penanganan __ main__ yang mungkin berbeda juga akan bagus di shell vs interaktif. Bagaimanapun, periksa semua fungsinya, dan lihat bagaimana rasanya tanpanya:

dir (__builtins__)
...
del __builtins__
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.