Python – How does MagicMock avoid throwing AttributeError when calling random methods?

How does MagicMock avoid throwing AttributeError when calling random methods?… here is a solution to the problem.

How does MagicMock avoid throwing AttributeError when calling random methods?

In Python, if you call a method that doesn’t exist, it throws an AttributeError. For example

>>> 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'

In the following code, the MagicMock class does not have a function named hello, or no patch is created for the method hello. The code still below does not throw an AttributeError

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

How does MagicMock do this? How do I create a class that can perform operations when any method (possibly undefined) is called?

Solution

The Python data model records a Hook __getattr__ , which should be called when property access cannot be resolved in the usual way. The simulation uses it to return a new simulation instance — that is, the simulation defines the unknown property as >factories

To reproduce the mock implementation in a simpler way, you just need to convert __getattr__ and __call__ to factory functions:

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

Example usage:

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

How is MagicMock able to do this?

This part is not specific to MagicMock, and regular mocks do the same thing (the “magic” in the name simply refers to the additional functionality that allows for better simulation< a href="https://docs.python.org/3/reference/datamodel.html#special-lookup" rel="noreferrer noopener nofollow">). magic methods)。 MagicMock inherits such behavior from one of the base classes :

>>> 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]

How can I create a class that can perform an action when any method (which might not be defined) is called on it?

It depends on whether you want to access the normal property before or after. If you want to be at the top of the list, you should define __getattribute__ , which is unconditionally called to implement property access before searching for the class/instance namespace. However, if you want to > on normal properties (that is, properties in object __dict__) and take a lower priority, then you should define __getattr__ as discussed earlier.