Perbedaan antara __getattr__ vs __getattribute__

Jawaban:

497

Perbedaan utama antara __getattr__dan __getattribute__itu __getattr__hanya dipanggil jika atribut tidak ditemukan dengan cara biasa. Ini bagus untuk menerapkan fallback untuk atribut yang hilang, dan mungkin salah satu dari dua yang Anda inginkan.

__getattribute__dipanggil sebelum melihat atribut sebenarnya pada objek, dan bisa jadi sulit untuk diterapkan dengan benar. Anda dapat berakhir dalam rekursi tak terbatas dengan sangat mudah.

Kelas gaya baru berasal dari object, kelas gaya lama adalah mereka yang di Python 2.x tanpa kelas dasar eksplisit. Tetapi perbedaan antara kelas gaya lama dan gaya baru bukanlah yang penting ketika memilih antara __getattr__dan __getattribute__.

Anda hampir pasti menginginkannya __getattr__.

Ned Batchelder
sumber
6
Bisakah saya mengimplementasikan keduanya di kelas yang sama? Jika saya bisa, untuk apa mengimplementasikan keduanya?
Alcott
13
@Alcott: Anda dapat mengimplementasikan keduanya, tetapi saya tidak yakin mengapa Anda melakukannya. __getattribute__akan dipanggil untuk setiap akses, dan __getattr__akan dipanggil untuk waktu yang __getattribute__dimunculkan AttributeError. Mengapa tidak menyimpan semuanya dalam satu?
Ned Batchelder
10
@NedBatchelder, jika Anda ingin (secara kondisional) mengabaikan panggilan ke metode yang ada, Anda ingin menggunakannya __getattribute__.
Jace Browning
28
"Untuk menghindari rekursi tak terbatas dalam metode ini, implementasinya harus selalu memanggil metode kelas dasar dengan nama yang sama untuk mengakses atribut apa pun yang dibutuhkannya, misalnya objek .__ mendapatkan atribut __ (diri, nama)."
kmonsoor
1
@kmonsoor. Itu alasan yang bagus untuk mengimplementasikan keduanya. objec.__getattribute__meminta myclass.__getattr__dalam kondisi yang tepat.
Fisikawan Gila
138

Mari kita lihat beberapa contoh sederhana dari keduanya __getattr__dan __getattribute__metode sulap.

__getattr__

Python akan memanggil __getattr__metode setiap kali Anda meminta atribut yang belum ditentukan. Dalam contoh berikut, Hitungan kelas saya tidak__getattr__ metode. Sekarang pada pokoknya ketika saya mencoba mengakses keduanya obj1.mymindan obj1.mymaxatribut semuanya berfungsi dengan baik. Tetapi ketika saya mencoba mengakses obj1.mycurrentatribut - Python memberi sayaAttributeError: 'Count' object has no attribute 'mycurrent'

class Count():
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

Sekarang Count kelas saya memiliki __getattr__metode. Sekarang ketika saya mencoba mengakses obj1.mycurrentatribut - python mengembalikan saya apa pun yang telah saya terapkan di__getattr__ metode . Dalam contoh saya setiap kali saya mencoba memanggil atribut yang tidak ada, python membuat atribut itu dan mengaturnya ke nilai integer 0.

class Count:
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax    

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)

__getattribute__

Sekarang mari kita lihat __getattribute__metodenya. Jika Anda memiliki __getattribute__metode di kelas Anda, python memanggil metode ini untuk setiap atribut terlepas dari apakah itu ada atau tidak. Jadi mengapa kita membutuhkan __getattribute__metode? Salah satu alasan bagus adalah Anda dapat mencegah akses ke atribut dan membuatnya lebih aman seperti yang ditunjukkan pada contoh berikut.

Setiap kali seseorang mencoba mengakses atribut saya yang dimulai dengan substring 'cur' python memunculkanAttributeError pengecualian. Kalau tidak, ia mengembalikan atribut itu.

class Count:

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

Penting: Untuk menghindari rekursi tak terbatas di __getattribute__ metode, implementasinya harus selalu memanggil metode kelas dasar dengan nama yang sama untuk mengakses atribut apa pun yang diperlukan. Misalnya: object.__getattribute__(self, name)atau super().__getattribute__(item)tidakself.__dict__[item]

PENTING

Jika kelas Anda mengandung kedua getattr dan getAttribute sihir metode kemudian __getattribute__disebut pertama. Tetapi jika __getattribute__memunculkan AttributeErroreksepsi maka eksepsi akan diabaikan dan __getattr__metode akan dipanggil. Lihat contoh berikut:

class Count(object):

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattr__(self, item):
            self.__dict__[item]=0
            return 0

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
N Randhawa
sumber
1
Saya tidak yakin apa tepatnya kasus penggunaan untuk penggantian __getattribute__ tapi pasti ini bukan. Karena per contoh Anda, semua yang Anda lakukan __getattribute__adalah meningkatkan AttributeErrorpengecualian jika atribut tidak ada di __dict__objek; tetapi Anda tidak benar-benar membutuhkan ini karena ini adalah implementasi standar __getattribute__dan infact __getattr__adalah persis apa yang Anda butuhkan sebagai mekanisme mundur.
Rohit
@Rohit currentdidefinisikan pada instance Count(lihat __init__), jadi cukup naikkan AttributeErrorjika atributnya tidak ada apa yang terjadi - itu mengacu pada__getattr__ untuk semua nama yang dimulai dengan 'cur', termasuk current, tetapi juga curious, curly...
joelb
16

Ini hanyalah contoh berdasarkan penjelasan Ned Batchelder .

__getattr__ contoh:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

Dan jika contoh yang sama digunakan dengan __getattribute__Anda akan mendapatkan >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Simon K Bhatta4ya
sumber
9
Sebenarnya, ini mengerikan. __getattr__()Implementasi dunia nyata hanya menerima set terbatas nama atribut yang valid dengan meningkatkan AttributeErrornama atribut yang tidak valid, sehingga menghindari masalah yang halus dan sulit di-debug . Contoh ini tanpa syarat menerima semua nama atribut sebagai valid - penyalahgunaan aneh dan rawan kesalahan __getattr__(). Jika Anda ingin "kontrol total" atas pembuatan atribut seperti dalam contoh ini, Anda ingin __getattribute__().
Cecil Curry
3
@CecilCurry: Semua masalah yang Anda tautkan melibatkan pengembalian Tidak ada alih-alih nilai, yang tidak dilakukan jawaban ini. Apa yang salah dengan menerima semua nama atribut? Itu sama dengan a defaultdict.
Nick Matteo
2
Masalahnya adalah itu __getattr__akan dipanggil sebelum pencarian superclass. Ini tidak masalah untuk subkelas langsung object, karena satu-satunya metode yang benar-benar Anda pedulikan ada metode ajaib yang mengabaikan instance, tetapi untuk struktur pewarisan yang lebih kompleks, Anda benar-benar menghapus kemampuan untuk mewarisi apa pun dari induk.
Fisikawan Gila
1
@Simon K Bhatta4ya, pernyataan cetak terakhir Anda adalah komentar. Baik? Ini adalah kalimat yang panjang dan membosankan untuk dibaca (Orang harus menggulir banyak di sisi kanan). Bagaimana dengan meletakkan baris setelah bagian kode? Atau jika Anda ingin meletakkannya di bagian kode, saya pikir akan lebih baik untuk memecahnya menjadi dua baris yang berbeda.
Ny. Abu Nafee Ibna Zahid
6

Kelas-kelas gaya baru adalah kelas-kelas yang mengelompokkan "objek" (langsung atau tidak langsung). Mereka memiliki __new__metode kelas sebagai tambahan__init__ dan memiliki perilaku tingkat rendah yang agak lebih rasional.

Biasanya, Anda ingin menimpanya __getattr__ (jika Anda keduanya), jika tidak, Anda akan kesulitan mendukung sintaks "self.foo" dalam metode Anda.

Info tambahan: http://www.devx.com/opensource/Article/31482/0/page/4

Tuan Fooz
sumber
2

Dalam membaca Beazley & Jones PCB, saya menemukan case-use yang eksplisit dan praktis __getattr__ membantu menjawab bagian "kapan" dari pertanyaan OP. Dari buku:

" __getattr__()Metodenya seperti catch-all untuk pencarian atribut. Ini adalah metode yang dipanggil jika kode mencoba mengakses atribut yang tidak ada." Kami tahu ini dari jawaban di atas, tetapi dalam resep PCB 8.15, fungsi ini digunakan untuk menerapkan pola desain delegasi . Jika Objek A memiliki atribut Objek B yang mengimplementasikan banyak metode yang ingin didelegasikan Objek A, daripada mendefinisikan ulang semua metode Obyek B di Objek A hanya untuk memanggil metode Objek B, tentukan __getattr__()metode sebagai berikut:

def __getattr__(self, name):
    return getattr(self._b, name)

di mana _b adalah nama atribut Object A yang merupakan Object B. Ketika sebuah metode yang didefinisikan pada Object B disebut pada Object A, __getattr__metode tersebut akan dipanggil pada akhir rantai pencarian. Ini akan membuat kode lebih bersih juga, karena Anda tidak memiliki daftar metode yang ditentukan hanya untuk mendelegasikan ke objek lain.

soporific312
sumber