Bagaimana cara mengganti __getattr__ dengan Python tanpa melanggar perilaku default?

185

Saya ingin mengganti __getattr__metode pada kelas untuk melakukan sesuatu yang mewah tetapi saya tidak ingin merusak perilaku default.

Apa cara yang benar untuk melakukan ini?

heats
sumber
20
"Hancurkan," katanya, bukan "berubah." Itu cukup jelas: atribut "mewah" tidak boleh mengganggu atribut bawaan dan harus berperilaku sebanyak mungkin seperti mereka. Jawaban Michael benar dan membantu.
olooney

Jawaban:

268

Mengesampingkan __getattr__harus baik - __getattr__hanya disebut sebagai upaya terakhir yaitu jika tidak ada atribut dalam contoh yang cocok dengan namanya. Misalnya, jika Anda mengakses foo.bar, maka __getattr__hanya akan dipanggil jika footidak memiliki atribut yang dipanggil bar. Jika atributnya adalah atribut yang tidak ingin Anda tangani, naikkan AttributeError:

class Foo(object):
    def __getattr__(self, name):
        if some_predicate(name):
            # ...
        else:
            # Default behaviour
            raise AttributeError

Namun, tidak seperti __getattr__, __getattribute__akan dipanggil dulu (hanya berfungsi untuk kelas gaya baru yaitu yang diwariskan dari objek). Dalam hal ini, Anda dapat mempertahankan perilaku default seperti:

class Foo(object):
    def __getattribute__(self, name):
        if some_predicate(name):
            # ...
        else:
            # Default behaviour
            return object.__getattribute__(self, name)

Lihat dokumen Python untuk informasi lebih lanjut .

Michael Williamson
sumber
Bah, hasil edit Anda memiliki hal yang sama dengan yang saya tunjukkan dalam jawaban saya, +1.
12
Keren, Python sepertinya tidak suka memanggil super dalam __getattr__- ada ide apa yang harus dilakukan? ( AttributeError: 'super' object has no attribute '__getattr__')
gatoatigrado
1
Tanpa melihat kode Anda, sulit untuk mengatakannya, tetapi sepertinya tidak ada superclasses Anda yang mendefinisikan getattr .
Colin vH
Ini bekerja dengan hasattr juga karena: "Ini diterapkan dengan memanggil getattr (objek, nama) dan melihat apakah ia memunculkan pengecualian atau tidak." docs.python.org/2/library/functions.html#hasattr
ShaBANG
1
-1 Ini memang memodifikasi perilaku default. Sekarang Anda memiliki AttributeErrortanpa konteks atribut dalam args pengecualian.
wim
34
class A(object):
    def __init__(self):
        self.a = 42

    def __getattr__(self, attr):
        if attr in ["b", "c"]:
            return 42
        raise AttributeError("%r object has no attribute %r" %
                             (self.__class__.__name__, attr))

>>> a = A()
>>> a.a
42
>>> a.b
42
>>> a.missing
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __getattr__
AttributeError: 'A' object has no attribute 'missing'
>>> hasattr(a, "b")
True
>>> hasattr(a, "missing")
False
wim
sumber
Terima kasih untuk ini. Hanya ingin memastikan saya memiliki pesan default yang benar tanpa menggali sumbernya.
ShawnFumo
4
Saya pikir itu self.__class__.__name__harus digunakan sebagai pengganti self.__class__kalau-kalau kelas menimpa__repr__
Michael Scott Cuthbert
3
Ini lebih baik daripada jawaban yang diterima, tapi alangkah baiknya tidak harus menulis ulang kode itu dan berpotensi kehilangan perubahan hulu jika kata-katanya diubah atau konteks tambahan ditambahkan ke objek pengecualian di masa depan.
wim
13

Untuk memperluas jawaban Michael, jika Anda ingin mempertahankan perilaku default menggunakan __getattr__, Anda dapat melakukannya seperti:

class Foo(object):
    def __getattr__(self, name):
        if name == 'something':
            return 42

        # Default behaviour
        return self.__getattribute__(name)

Sekarang pesan pengecualian lebih deskriptif:

>>> foo.something
42
>>> foo.error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __getattr__
AttributeError: 'Foo' object has no attribute 'error'
José Luis
sumber
2
@ fed.pavlo Anda yakin? Mungkin Anda campur __getattr__dan __getattribute__?
José Luis
salahku. Saya melewatkan panggilan ke metode yang berbeda dari diri saya sendiri. ; (
fed.pavlo
2
Memang jawaban
@Michael