Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does pytest.approx accomplish its magic?

Tags:

python

pytest

The pytest approx function seems really, cool, and as they say in their documentation it uses a really intuitive syntax:

>>> from pytest import approx
>>> 0.1 + 0.2 == approx(0.3)
True
>>> 1 + 1e-8 == approx(1)
True

But how does this actually work? In the first example, let's say the left side reduces to something like 0.29999..., so how can I have something on the right side that evaluates to being equal to it? Does the approx function somehow know how to look at the lvalue of the == operator? The fact that approx actually works seems like pure sorcery, can someone explain how it accomplishes its neat little trick?

like image 354
Mr. Snrub Avatar asked May 14 '19 03:05

Mr. Snrub


People also ask

What is Pytest_configure?

pytest allows you to create custom plugins for this sort of functionality. But it also allows you to register those same plugin hooks in your conftest.py files. The above code in your tests/conftest.py file will do the trick. pytest_configure() registers a new marker ( focus ).

Where do I put pytest files?

While the pytest discovery mechanism can find tests anywhere, pytests must be placed into separate directories from the product code packages. These directories may either be under the project root or under the Python package.


2 Answers

This is a standard datamodel hook into a custom __eq__.

The simplified example below should clarify the "sorcery" for you.

>>> class MyObj: 
...     def __eq__(self, other): 
...         print(self, "is being compared with", other) 
...         return "potato" 
...
>>> obj = MyObj()   
>>> 0.1 + 0.2 == obj
<__main__.MyObj object at 0xcafef00d> is being compared with 0.30000000000000004
'potato'

Note that float.__eq__ will get the first attempt at handling this comparison. The behavior shown above, and also for approx(0.3), crucially relies on the fact that float has explicitly "opted out" of comparing with MyObj instances. It does this by returning a special value NotImplemented:

>>> (0.1+0.2).__eq__(obj)
NotImplemented

For the actual pytest implementation, look in python_api.py::ApproxScalar.

like image 141
wim Avatar answered Sep 21 '22 16:09

wim


From the source code I could find that they create corresponding Approx version of the classes. For example, they have ApproxDecimal, ApproxScalar, ApproxMapping and so on. The approx function checks the type of the value you pass and then assigns it a corresponding approximate class that they have defined.

So when you enter:

0.1 + 0.2 == approx(0.3)

the approx changes it to:

0.1 + 0.2 == ApproxDecimal(0.3)

Now these Approx classes implement corresponding __eq__() and __repr__() functions that help python perform the comparison. Hence they are able to define the logic for approximate matching within these Approximate classes.

like image 41
razdi Avatar answered Sep 19 '22 16:09

razdi