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?
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 ).
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.
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With