Memahami perbedaan antara __getattr__ dan __getattribute__

206

Saya mencoba memahami perbedaan antara __getattr__dan __getattribute__, bagaimanapun, saya gagal dalam hal itu.

The jawaban ke Stack Overflow pertanyaan Perbedaan antara __getattr__vs__getattribute__ mengatakan:

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

Saya sama sekali tidak tahu apa artinya itu.

Kemudian dikatakan:

Anda hampir pasti menginginkannya __getattr__.

Mengapa?

Saya membaca bahwa jika __getattribute__gagal, __getattr__dipanggil. Jadi mengapa ada dua metode berbeda melakukan hal yang sama? Jika kode saya mengimplementasikan kelas gaya baru, apa yang harus saya gunakan?

Saya mencari beberapa contoh kode untuk menghapus pertanyaan ini. Saya telah menggunakan Google untuk yang terbaik dari kemampuan saya, tetapi jawaban yang saya temukan tidak membahas masalah secara menyeluruh.

Jika ada dokumentasi, saya siap membacanya.

pengguna225312
sumber
2
jika itu membantu, dari dokumen "Untuk menghindari rekursi tak terbatas dalam metode ini, implementasinya harus selalu memanggil metode kelas dasar dengan nama yang sama untuk mengakses atribut yang dibutuhkannya, misalnya, objek. " [ docs.python.org/2/reference/…
kmonsoor

Jawaban:

306

Beberapa dasar dulu.

Dengan objek, Anda harus berurusan dengan atributnya. Biasanya kita lakukan instance.attribute. Terkadang kita perlu lebih banyak kontrol (ketika kita tidak tahu nama atribut sebelumnya).

Sebagai contoh, instance.attributeakan menjadi getattr(instance, attribute_name). Dengan menggunakan model ini, kita bisa mendapatkan atribut dengan memasok atribut_name sebagai string.

Penggunaan __getattr__

Anda juga bisa memberi tahu kelas bagaimana menangani atribut yang tidak dikelola secara eksplisit dan melakukannya melalui __getattr__metode.

Python akan memanggil metode ini setiap kali Anda meminta atribut yang belum ditentukan, sehingga Anda dapat menentukan apa yang harus dilakukan dengannya.

Kasus penggunaan klasik:

class A(dict):
    def __getattr__(self, name):
       return self[name]
a = A()
# Now a.somekey will give a['somekey']

Peringatan dan penggunaan __getattribute__

Jika Anda perlu menangkap setiap atribut terlepas dari apakah itu ada atau tidak , gunakan __getattribute__saja. Perbedaannya adalah bahwa __getattr__hanya dipanggil untuk atribut yang tidak benar-benar ada. Jika Anda menetapkan atribut secara langsung, merujuk atribut itu akan mengambilnya tanpa menelepon __getattr__.

__getattribute__ disebut sepanjang waktu.

pyfunc
sumber
"Sebagai contoh, instance.attribute akan menjadi getattr(instance, attribute_name)." Bukankah seharusnya begitu __getattribute__(instance, attribute_name)?
Ny. Abu Nafee Ibna Zahid
1
@ Md.AbuNafeeIbnaZahid getattradalah fungsi bawaan . getattr(foo, 'bar')setara dengan foo.bar.
wizzwizz4
Perlu dicatat bahwa __getattr__hanya akan dipanggil jika sudah ditentukan, karena tidak diwarisi dariobject
joelb
94

__getattribute__ dipanggil setiap kali akses atribut terjadi.

class Foo(object):
    def __init__(self, a):
        self.a = 1

    def __getattribute__(self, attr):
        try:
            return self.__dict__[attr]
        except KeyError:
            return 'default'
f = Foo(1)
f.a

Ini akan menyebabkan rekursi yang tak terbatas. Pelakunya di sini adalah garis return self.__dict__[attr]. Mari kita berpura-pura (Sudah cukup dekat dengan kebenaran) bahwa semua atribut disimpan self.__dict__dan tersedia dengan namanya. Garis

f.a

mencoba mengakses aatribut dari f. Ini panggilan f.__getattribute__('a'). __getattribute__kemudian mencoba memuat self.__dict__. __dict__adalah atribut self == fdan panggilan python f.__getattribute__('__dict__')yang lagi mencoba mengakses atribut '__dict__'. Ini adalah rekursi yang tak terbatas.

Jika __getattr__telah digunakan sebagai gantinya

  1. Itu tidak akan pernah berjalan karena fmemiliki aatribut.
  2. Jika sudah berjalan, (katakanlah Anda meminta f.b), maka itu tidak akan dipanggil untuk menemukan __dict__karena sudah ada dan __getattr__dipanggil hanya jika semua metode lain untuk menemukan atribut telah gagal .

Cara 'benar' untuk menulis menggunakan kelas di atas __getattribute__adalah

class Foo(object):
    # Same __init__

    def __getattribute__(self, attr):
        return super(Foo, self).__getattribute__(attr)

super(Foo, self).__getattribute__(attr)mengikat __getattribute__metode superclass 'terdekat' (secara formal, kelas berikutnya dalam Metode Resolution Order kelas, atau MRO) ke objek saat ini selfdan kemudian memanggilnya dan memungkinkan yang melakukan pekerjaan.

Semua masalah ini dihindari dengan menggunakan __getattr__yang memungkinkan Python melakukan hal yang normal sampai atribut tidak ditemukan. Pada titik itu, Python menyerahkan kontrol ke __getattr__metode Anda dan membiarkannya menghasilkan sesuatu.

Perlu juga dicatat bahwa Anda dapat mengalami rekursi tak terbatas __getattr__.

class Foo(object):
    def __getattr__(self, attr):
        return self.attr

Saya akan meninggalkan yang itu sebagai latihan.

aaronasterling
sumber
60

Saya pikir jawaban lain telah melakukan pekerjaan yang bagus untuk menjelaskan perbedaan antara __getattr__dan __getattribute__, tetapi satu hal yang mungkin tidak jelas adalah mengapa Anda ingin menggunakannya __getattribute__. Hal yang paling keren __getattribute__adalah bahwa pada dasarnya memungkinkan Anda untuk membebani titik saat mengakses kelas. Ini memungkinkan Anda untuk menyesuaikan cara atribut diakses di tingkat rendah. Sebagai contoh, misalkan saya ingin mendefinisikan kelas di mana semua metode yang hanya mengambil argumen sendiri diperlakukan sebagai properti:

# prop.py
import inspect

class PropClass(object):
    def __getattribute__(self, attr):
        val = super(PropClass, self).__getattribute__(attr)
        if callable(val):
            argcount = len(inspect.getargspec(val).args)
            # Account for self
            if argcount == 1:
                return val()
            else:
                return val
        else:
            return val

Dan dari interpreter interaktif:

>>> import prop
>>> class A(prop.PropClass):
...     def f(self):
...             return 1
... 
>>> a = A()
>>> a.f
1

Tentu saja ini adalah contoh konyol dan Anda mungkin tidak akan pernah mau melakukan ini, tetapi ini menunjukkan kekuatan yang bisa Anda dapatkan dari mengganti __getattribute__.

Jason Baker
sumber
6

Saya telah melalui penjelasan luar biasa dari yang lain. Namun, saya menemukan jawaban sederhana dari blog ini Python Sihir Metode dan__getattr__ . Semua yang berikut berasal dari sana.

Dengan menggunakan __getattr__metode ajaib, kita dapat mencegat pencarian atribut yang tidak ada dan melakukan sesuatu agar tidak gagal:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one  # 'WHAT_ABOUT_THIS_ONE'

Tetapi jika atribut itu ada, __getattr__tidak akan dipanggil:

class Dummy(object):

    def __getattr__(self, attr):
        return attr.upper()

d = Dummy()
d.value = "Python"
print(d.value)  # "Python"

__getattribute__mirip dengan __getattr__, dengan perbedaan penting yang __getattribute__akan mencegat SETIAP pencarian atribut, tidak masalah apakah atribut itu ada atau tidak.

class Dummy(object):

    def __getattribute__(self, attr):
        return 'YOU SEE ME?'

d = Dummy()
d.value = "Python"
print(d.value)  # "YOU SEE ME?"

Dalam contoh itu, dobjek sudah memiliki nilai atribut. Tetapi ketika kami mencoba mengaksesnya, kami tidak mendapatkan nilai yang diharapkan asli ("Python"); kami hanya mendapatkan apa pun yang __getattribute__dikembalikan. Ini berarti bahwa kita hampir kehilangan atribut nilai; itu telah menjadi "tidak terjangkau".

mathsyouth
sumber