Repository: microsoft/Qcodes

Issue:
Settable parameters as context managers
It might be useful for parameters which can be set to act as context managers, such that user code can be guaranteed that a parameter is returned to its original value after a block of code. For instance:

```python
>>> v = dac.voltage()
>>> with dac.voltage(-1):
...     # Do stuff with the DAC output set to -1 V.
...
>>> assert abs(dac.voltage() - v) <= tolerance
```
@QCoDeS/core 


Discussion:
I like it. If anyone takes a stab at implementing this, they won't be wasting their time. 
Sohail & Mikhail will take a stab
working on branch https://github.com/sohailc/Qcodes/tree/feature/settable_parameters

Resolving patch:
diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py
--- a/qcodes/instrument/parameter.py
+++ b/qcodes/instrument/parameter.py
@@ -78,6 +78,29 @@
 
 Number = Union[float, int]
 
+
+class _SetParamContext:
+    """
+    This class is returned by the set method of parameters
+
+    Example usage:
+    >>> v = dac.voltage()
+    >>> with dac.voltage(-1):
+        ...     # Do stuff with the DAC output set to -1 V.
+        ...
+    >>> assert abs(dac.voltage() - v) <= tolerance
+    """
+    def __init__(self, parameter):
+        self._parameter = parameter
+        self._original_value = self._parameter._latest["value"]
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, typ, value, traceback):
+        self._parameter.set(self._original_value)
+
+
 class _BaseParameter(Metadatable):
     """
     Shared behavior for all parameters. Not intended to be used
@@ -449,6 +472,7 @@ def set_wrapper(value, **kwargs):
                     if t_elapsed < self.post_delay:
                         # Sleep until total time is larger than self.post_delay
                         time.sleep(self.post_delay - t_elapsed)
+
             except Exception as e:
                 e.args = e.args + ('setting {} to {}'.format(self, value),)
                 raise e
@@ -680,6 +704,22 @@ def root_instrument(self) -> Optional['InstrumentBase']:
         else:
             return None
 
+    def set_to(self, value):
+        """
+        Use a context manager to temporarily set the value of a parameter to
+        a value. Example:
+
+        >>> from qcodes import Parameter
+        >>> p = Parameter("p", set_cmd=None, get_cmd=None)
+        >>> with p.set_to(3):
+        ...    print(f"p value in with block {p.get()}")
+        >>> print(f"p value outside with block {p.get()}")
+        """
+        context_manager = _SetParamContext(self)
+        self.set(value)
+        return context_manager
+
+
 class Parameter(_BaseParameter):
     """
     A parameter that represents a single degree of freedom.


Test patch:
diff --git a/qcodes/tests/test_parameter.py b/qcodes/tests/test_parameter.py
--- a/qcodes/tests/test_parameter.py
+++ b/qcodes/tests/test_parameter.py
@@ -902,3 +902,30 @@ def tearDown(self):
         self.d.close()
         del self.a
         del self.d
+
+
+class TestSetContextManager(TestCase):
+
+    def setUp(self):
+        self.instrument = DummyInstrument('dummy_holder')
+        self.instrument.add_parameter(
+            "a",
+            set_cmd=None,
+            get_cmd=None
+        )
+
+    def tearDown(self):
+        self.instrument.close()
+        del self.instrument
+
+    def test_none_value(self):
+        with self.instrument.a.set_to(3):
+            assert self.instrument.a.get() == 3
+        assert self.instrument.a.get() is None
+
+    def test_context(self):
+        self.instrument.a.set(2)
+
+        with self.instrument.a.set_to(3):
+            assert self.instrument.a.get() == 3
+        assert self.instrument.a.get() == 2