Repository: Parquery/icontract

Issue:
AttributeError in contract caused by method call in constructor
When a method is called in the constructor, an `AttributeError` is raised in the contract. Here is a reproducer:

```Python
from icontract import invariant


@invariant(lambda self: all(" " not in part for part in self.parts))
class ID:
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(self.separator())

    def separator(self) -> str:
        return "."


ID("A")
```

```Console
$ python test.py  
Traceback (most recent call last):
  File "test.py", line 14, in <module>
    ID("A")
  File "/home/tr/.local/lib/python3.8/site-packages/icontract/_checkers.py", line 461, in wrapper
    result = func(*args, **kwargs)
  File "test.py", line 7, in __init__
    self.parts = identifier.split(self.separator)
  File "/home/tr/.local/lib/python3.8/site-packages/icontract/_checkers.py", line 498, in wrapper
    _assert_invariant(contract=contract, instance=instance)
  File "/home/tr/.local/lib/python3.8/site-packages/icontract/_checkers.py", line 162, in _assert_invariant
    check = contract.condition(self=instance)
  File "test.py", line 4, in <lambda>
    @invariant(lambda self: all(" " not in part for part in self.parts))
AttributeError: 'ID' object has no attribute 'parts'
```

The contract works correctly when `self.separator()` is replaced by a string literal.

### Versions

icontract  2.3.4
asttokens  2.0.4
Python  3.8.5

Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 383](https://coveralls.io/builds/33455094)

* **1** of **1**   **(100.0%)**  changed or added relevant line in **1** file are covered.
* No unchanged relevant lines lost coverage.
* Overall coverage decreased (**-0.2%**) to **94.156%**

---



|  Totals | [![Coverage Status](https://coveralls.io/builds/33455094/badge)](https://coveralls.io/builds/33455094) |
| :-- | --: |
| Change from base [Build 381](https://coveralls.io/builds/33455032): |  -0.2% |
| Covered Lines: | 999 |
| Relevant Lines: | 1061 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/README.rst b/README.rst
index 12c3b36..62fdfc3 100644
--- a/README.rst
+++ b/README.rst
@@ -711,6 +711,19 @@ in progress and removed once the invariants checking finished. As long as the du
 ``__dbc_invariant_check_is_in_progress__`` is present, the wrappers that check invariants simply return the result of
 the function.
 
+Invariant checks also need to be disabled during the construction since calling member functions would trigger invariant
+checks which, on their hand, might check on yet-to-be-defined instance attributes. See the following snippet:
+
+.. code-block:: python
+
+        @icontract.invariant(lambda self: self.some_attribute > 0)
+        class SomeClass(icontract.DBC):
+            def __init__(self) -> None:
+                self.some_attribute = self.some_func()
+
+            def some_func(self) -> int:
+                return 1984
+
 Linter
 ------
 We provide a linter that statically verifies the arguments of the contracts (*i.e.* that they are
@@ -797,9 +810,9 @@ Benchmarking invariant at __init__:
 =========================  ============  ==============  =======================
 Case                         Total time    Time per run    Relative time per run
 =========================  ============  ==============  =======================
-`ClassWithIcontract`             1.37 s         1.37 μs                     296%
-`ClassWithDpcontracts`           0.46 s         0.46 μs                     100%
-`ClassWithInlineContract`        0.27 s         0.27 μs                      59%
+`ClassWithIcontract`             1.43 s         1.43 μs                     306%
+`ClassWithDpcontracts`           0.47 s         0.47 μs                     100%
+`ClassWithInlineContract`        0.27 s         0.27 μs                      57%
 =========================  ============  ==============  =======================
 
 Benchmarking invariant at a function:
@@ -807,9 +820,9 @@ Benchmarking invariant at a function:
 =========================  ============  ==============  =======================
 Case                         Total time    Time per run    Relative time per run
 =========================  ============  ==============  =======================
-`ClassWithIcontract`             2.14 s         2.14 μs                     452%
-`ClassWithDpcontracts`           0.47 s         0.47 μs                     100%
-`ClassWithInlineContract`        0.25 s         0.25 μs                      53%
+`ClassWithIcontract`             2.00 s         2.00 μs                     445%
+`ClassWithDpcontracts`           0.45 s         0.45 μs                     100%
+`ClassWithInlineContract`        0.23 s         0.23 μs                      52%
 =========================  ============  ==============  =======================
 
 Benchmarking precondition:
@@ -817,9 +830,9 @@ Benchmarking precondition:
 ===============================  ============  ==============  =======================
 Case                               Total time    Time per run    Relative time per run
 ===============================  ============  ==============  =======================
-`function_with_icontract`              0.02 s         2.41 μs                       5%
-`function_with_dpcontracts`            0.53 s        53.20 μs                     100%
-`function_with_inline_contract`        0.00 s         0.16 μs                       0%
+`function_with_icontract`              0.02 s         2.38 μs                       5%
+`function_with_dpcontracts`            0.51 s        50.89 μs                     100%
+`function_with_inline_contract`        0.00 s         0.15 μs                       0%
 ===============================  ============  ==============  =======================
 
 Benchmarking postcondition:
@@ -827,9 +840,9 @@ Benchmarking postcondition:
 ===============================  ============  ==============  =======================
 Case                               Total time    Time per run    Relative time per run
 ===============================  ============  ==============  =======================
-`function_with_icontract`              0.03 s         2.51 μs                       5%
-`function_with_dpcontracts`            0.52 s        52.42 μs                     100%
-`function_with_inline_contract`        0.00 s         0.17 μs                       0%
+`function_with_icontract`              0.02 s         2.48 μs                       5%
+`function_with_dpcontracts`            0.51 s        50.93 μs                     100%
+`function_with_inline_contract`        0.00 s         0.15 μs                       0%
 ===============================  ============  ==============  =======================
 
 
diff --git a/icontract/_checkers.py b/icontract/_checkers.py
index d499064..7882875 100644
--- a/icontract/_checkers.py
+++ b/icontract/_checkers.py
@@ -458,15 +458,17 @@ def _decorate_with_invariants(func: CallableT, is_init: bool) -> CallableT:
 
         def wrapper(*args, **kwargs):  # type: ignore
             """Wrap __init__ method of a class by checking the invariants *after* the invocation."""
-            result = func(*args, **kwargs)
             instance = _find_self(param_names=param_names, args=args, kwargs=kwargs)
             assert instance is not None, "Expected to find `self` in the parameters, but found none."
 
+            # We need to disable the invariants check during the constructor.
             id_instance = str(id(instance))
             setattr(_IN_PROGRESS, id_instance, True)
 
             # ExitStack is not used here due to performance.
             try:
+                result = func(*args, **kwargs)
+
                 for contract in instance.__class__.__invariants__:
                     _assert_invariant(contract=contract, instance=instance)
 


Test patch:
diff --git a/tests/test_recursion.py b/tests/test_recursion.py
index 3ca0eb5..db46ebf 100644
--- a/tests/test_recursion.py
+++ b/tests/test_recursion.py
@@ -227,6 +227,23 @@ class TestInvariant(unittest.TestCase):
         some_instance.another_func()
         self.assertListEqual(['some_func', 'another_func', 'some_func'], order)
 
+    def test_member_function_call_in_constructor(self) -> None:
+        order = []  # type: List[str]
+
+        @icontract.invariant(lambda self: self.some_attribute > 0)  # pylint: disable=no-member
+        class SomeClass(icontract.DBC):
+            def __init__(self) -> None:
+                order.append('__init__ enters')
+                self.some_attribute = self.some_func()
+                order.append('__init__ exits')
+
+            def some_func(self) -> int:
+                order.append('some_func')
+                return 3
+
+        _ = SomeClass()
+        self.assertListEqual(['__init__ enters', 'some_func', '__init__ exits'], order)
+
 
 if __name__ == '__main__':
     unittest.main()