# Context Management¶

The `FixedPoint`

class offers richly-featured context management (see
**PEP 343**) that allows for some unique approaches to programatic arithmetic.
Three functions are utilized:

`FixedPoint.__call__()`

allows properties to be assigned in the
with statement at the start of the context;
this is called context initialization.

`FixedPoint.__enter__()`

save off the current state of the
`FixedPoint`

and assigns the properties specified by
`__call__()`

in the new context.

`FixedPoint.__exit__()`

restores the original context unless the
`safe_retain`

keyword was specified in `__call__()`

.

## Basic Usage¶

Use the with statement to generate a scope in which changes to the original object can be undone:

```
>>> from fixedpoint import FixedPoint
>>> x = FixedPoint(1/9, signed=1)
>>> x.qformat
'Q1.54'
>>> with x: # save off the current state of x
... x.signed = 0
... x.m = 42
... x.qformat # show the changes that were made within the context
'UQ42.54'
>>> x.qformat # outisde of the with context, original x is restored
'Q1.54'
```

Any property or attribute of the original `FixedPoint`

can be changed within
the context. All changes made to `FixedPoint`

properties are restored at
context exit. These properties include:

Even the value can be changed with Arithmetic or Initializers.

```
>>> float(x)
0.1111111111111111
>>> with x:
... x.from_string("0x7FFFFFAAAA5555")
... float(x)
-7.947407237862691e-08
>>> float(x)
0.1111111111111111
```

New `FixedPoint`

s generated inside the context manager are valid and available
outside of the context. This is useful for temporarily overriding properties.
You can also rename a variable if desired.

```
>>> x = FixedPoint(0.2)
>>> y = FixedPoint(0.7)
>>> x.qformat, y.qformat
('UQ0.54', 'UQ0.52')
>>> z = x - y
Traceback (most recent call last):
...
fixedpoint.FixedPointOverflowError: [SN3] Unsigned subtraction causes overflow.
>>> with x as xtmp, y as ytmp:
... xtmp.m, ytmp.m = 1, 1
... xtmp.signed, ytmp.signed = 1, 1
... z = x - y
... xtmp.qformat, ytmp.qformat, z.qformat
('Q1.54', 'Q1.52', 'Q2.54')
>>> x.qformat, y.qformat, z.qformat, float(round(z, 1))
('UQ0.54', 'UQ0.52', 'Q2.54', -0.5)
```

Context managers can be nested:

```
>>> def nest(x):
... print(f'0) {x.rounding=}')
... with x as y:
... print(f'1) {x.rounding=}')
... x.rounding = 'in'
... print(f'2) {x.rounding=}')
... with y as z:
... print(f'3) {x.rounding=}')
... z.rounding = 'convergent'
... print(f'4) {x.rounding=}')
... print(f'5) {x.rounding=}')
... print(f'6) {x.rounding=}')
>>> nest(FixedPoint(31))
0) x.rounding='nearest'
1) x.rounding='nearest'
2) x.rounding='in'
3) x.rounding='in'
4) x.rounding='convergent'
5) x.rounding='in'
6) x.rounding='nearest'
```

## Context Initialization¶

In addition to saving off the current context of `FixedPoint`

objects, the
with statement can also initialize the new
context for you. Given `x`

, `y`

, and `z`

below,

```
>>> x = FixedPoint(-1)
>>> y = FixedPoint(1, mismatch_alert='error')
>>> z = x + y
Traceback (most recent call last):
...
fixedpoint.MismatchError: [SN2] Non-matching mismatch_alert behaviors ['warning', 'error'].
```

the following two code blocks accomplish the same goal:

```
>>> with x, y:
... x.rounding = 'nearest'
... x.mismatch_alert = 'error'
... z = x + y
>>> float(z)
0.0
```

```
>>> with x(rounding='nearest', mismatch_alert='error'):
... z = x + y
>>> float(z)
0.0
```

Any keywordable argument from the `FixedPoint`

constructor can be used in
the context manager. All initilization arguments must be keyworded. The
`__call__()`

keywords can be specified in a `dict`

if
preferred.

```
>>> xprop = {'rounding': 'nearest', 'mismatch_alert': 'warning'}
>>> yprop = {'mismatch_alert': 'warning'}
>>> with x(**xprop), y(**yprop):
... z = x + y
>>> x.rounding, x.mismatch_alert, y.rounding, y.mismatch_alert
('convergent', 'warning', 'nearest', 'error')
```

## Retaining the Context¶

Context initialization also supports a `safe_retain`

keyword that, when
`True`

, will not restore the original `FixedPoint`

context as long as
no exceptions occur.

```
>>> x = FixedPoint(3, str_base=10)
>>> x.qformat
'UQ2.0'
>>> with x(safe_retain=True):
... x.signed = True
Traceback (most recent call last):
...
fixedpoint.FixedPointOverflowError: [SN1] Changing signedness on 3 causes overflow.
>>> x.signed, x.qformat # Changes were not retained because of exception
(False, 'UQ2.0')
>>> with x(m=3, safe_retain=True):
... x.signed = True
>>> x.signed, x.qformat # Changes were retained
(True, 'Q3.0')
```

This is useful when several properties/attributes might change, and if all
changes are made successfully, the properties should be retained. In fact,
this is exactly how `FixedPoint.resize()`

is implemented:

```
def resize(self: FixedPointType, m: int, n: int, /, rounding: str = None,
overflow: str = None, alert: str = None) -> None:
"""Resize integer and fractional bit widths.
Overflow handling, sign-extension, and rounding are employed.
Override rounding, overflow, and overflow_alert settings for the
scope of this method by specifying the appropriate arguments.
"""
old = self._overflow, self._rounding, self._overflow_alert
try:
with self(safe_retain=True,
overflow=overflow or self.overflow,
rounding=rounding or self.rounding,
overflow_alert=alert or self.overflow_alert):
self.n = n
self.m = m
except Exception:
raise
else:
self._overflow, self._rounding, self._overflow_alert = old
```

The magic here is that if `self.m = m`

raises an exception, then the
assignment on the line just before it is undone by the context manager. However,
if no exception occurs, then the assignments to the `m`

and `n`

attributes
are kept and the number is resized.