Как переопределить __getattr__ в Python, не нарушая поведение по умолчанию?

186

Я хочу переопределить __getattr__метод класса, чтобы сделать что-то необычное, но я не хочу нарушать поведение по умолчанию.

Какой правильный способ сделать это?

sheats
источник
20
«Перерыв», спрашивает он, а не «перемена». Это достаточно ясно: «причудливые» атрибуты не должны мешать встроенным атрибутам и должны вести себя как можно больше. Ответ Михаила и правильный, и полезный.
olooney

Ответы:

268

Переопределение __getattr__должно быть в порядке - __getattr__вызывается только в крайнем случае, т. Е. Если в экземпляре нет атрибутов, соответствующих имени. Например, если вы обращаетесь foo.bar, то __getattr__будет вызываться, только если fooне было вызвано ни одного атрибута bar. Если атрибут тот, который вы не хотите обрабатывать, поднимите AttributeError:

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

Однако, в отличие от __getattr__, __getattribute__будет вызываться первым (работает только для новых стилевых классов, то есть тех, которые наследуются от объекта). В этом случае вы можете сохранить поведение по умолчанию следующим образом:

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

Смотрите документы Python для получения дополнительной информации .

Майкл Уильямсон
источник
Бах, твое редактирование имеет то же самое, что я показал в своем ответе, +1.
12
Круто, Python, похоже, не любит называть super's __getattr__- есть идеи, что делать? ( AttributeError: 'super' object has no attribute '__getattr__')
gatoatigrado
1
Трудно сказать, не видя ваш код, но похоже, что ни один из ваших суперклассов не определяет getattr .
Colin vH
Это работает с hasattr также потому, что: «Это реализуется путем вызова getattr (object, name) и выяснения, вызывает ли оно исключение или нет». docs.python.org/2/library/functions.html#hasattr
ShaBANG
1
-1 Это действительно изменить поведение по умолчанию. Теперь у вас есть AttributeErrorконтекст без атрибута в аргументах исключения.
Вим
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
источник
Спасибо за это. Просто хотел убедиться, что у меня правильное сообщение по умолчанию, не копаясь в источнике.
ShawnFumo
4
Я думаю, что это self.__class__.__name__следует использовать, а не self.__class__в случае переопределения класса__repr__
Майкл Скотт Катберт
3
Это лучше, чем принятый ответ, но было бы неплохо не переписывать этот код и, возможно, пропустить исходные изменения, если в будущем будет изменена формулировка или добавлен дополнительный контекст к объекту исключения.
Вим
14

Чтобы расширить ответ Михаэля, если вы хотите сохранить поведение по умолчанию, используя его __getattr__, вы можете сделать это так:

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

        # Default behaviour
        return self.__getattribute__(name)

Теперь сообщение об исключении более наглядно:

>>> 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'
Хосе Луис
источник
2
@ fed.pavlo ты уверен? Может быть , вы смешано __getattr__и __getattribute__?
Хосе Луис
моя вина. Я пропустил вызов другого метода от себя. ; (
fed.pavlo
2
В самом деле, ответ @ Майкла неполон без этого ответа
Grijesh Chauhan