Как MagicMock избегает выброса AttributeError при вызове случайного метода? - PullRequest
0 голосов
/ 25 октября 2018

В Python, если вы вызываете метод, который не существует, он выдает AttributeError.Пример

>>> class A:
...     def yo(self):
...             print(1)
... 
>>> a = A()
>>> a.yo()
1
>>> a.hello()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'hello'

В приведенном ниже коде класс MagicMock не имеет функции с именем hello или патч не был создан для метода hello.В приведенном ниже коде не генерируется ошибка AttributeError

>>> from unittest.mock import MagicMock 
>>> obj = MagicMock()
>>> obj.hello()
<MagicMock name='mock.hello()' id='4408758568'>

Как MagicMock может это сделать?Как я могу создать класс, который может выполнять действие, когда к нему вызывается любой метод (который не может быть определен)?

Ответы [ 2 ]

0 голосов
/ 25 октября 2018

Модель данных Python документирует хук, __getattr__, который должен вызываться, когда доступ к атрибуту не удается разрешить обычными способами.Mocks используют его для возврата нового экземпляра mock - то есть mocks определяют неизвестные атрибуты как фабрики .

Воспроизводя реализацию mock более простым способом, вы бы просто включили __getattr__ и __call__ в заводские функции:

class M:
    def __call__(self):
        return M()
    def __getattr__(self, name):
        return M()

Пример использования:

>>> mock = M()
>>> mock.potato
<__main__.M at 0xdeadbeef>
>>> mock.potato()
<__main__.M at 0xcafef00d>

Как MagicMock может это сделать?

Эта часть не относится к MagicMock, обычный Mock будет делать то же самое («магия» в названии просто относится к дополнительным функциям, позволяющим лучше высмеивать магию )методы ).MagicMock наследует такое поведение от одного из базовых классов :

>>> MagicMock.mro()
[unittest.mock.MagicMock,
 unittest.mock.MagicMixin,
 unittest.mock.Mock,
 unittest.mock.CallableMixin,
 unittest.mock.NonCallableMock,  # <--- this one overrides __getattr__!
 unittest.mock.Base,
 object]

Как создать класс, который может выполнять действие при любом методе (который может бытьопределено) вызывается на нем?

Зависит от того, хотите ли вы быть перед или за нормальным доступом к атрибуту.Если вы хотите выйти вперед, вы должны определить __getattribute__, он вызывается безусловно для реализации доступа к атрибутам перед поиском в пространствах имен класса / экземпляра.Однако, если вы хотите иметь более низкий приоритет перед обычными атрибутами (то есть теми, которые живут в объекте __dict__) и дескрипторами , тогда вы должны определить __getattr__, как обсуждалось ранее.

0 голосов
/ 25 октября 2018

На самом деле я не знаю, как конкретно MagicMock работает (я никогда не использовал его, но слышал хорошие вещи), но эта часть поведения может быть воспроизведена (наряду с, вероятно, множеством других возможных решений)угоня __getattr__ таким образом, что он возвращает вызываемый объект, который создает новый экземпляр фиктивного файла при вызове:

class MM:
    def __init__(self, name=None):
        # store a name, TODO: random id, etc.
        self.name = name

    def __repr__(self):
        # make it pretty
        if self.name:
            r = f'<MM name={self.name}>'
        else:
            r = f'<MM>'
        return r

    def __getattr__(self, attrname):
        # we want a factory for a mock instance with a name corresponding to attrname
        def magicattr():
            return MM(name=f"'mock.{attrname}()'")
        return magicattr

При выполнении мы видим следующее:

>>> MM()
<MM>
>>> MM().hello()
<MM name='mock.hello()'>

Я не сделалНе стоит забывать об определении id и еще чего-нибудь, но основной трюк можно увидеть на приведенном выше урезанном примере.

Способ, с которым работает выше, заключается в том, что доступ к .hello или любому другому атрибуту идетчерез наш пользовательский __getattr__, который дает нам возможность на лету генерировать фальшивый (насмешливый) метод с любыми свойствами, которые мы хотим.Как я понимаю, одно из многих преимуществ MagicMock заключается именно в том, что нам не нужно беспокоиться о том, что AttributeError s выдается по умолчанию, просто работает .

...