Repository: microsoft/Qcodes

Issue:
Error when importing Agilent 34400A introduced with "recent" commit 
Hi all,

After one of my student updated his Qcodes repository on his computer, he got the following error while importing the Agilent 34400A driver:
```python
TypeError Traceback (most recent call last)
<ipython-input-2-5ff6cb0e0787> in <module>
----> 1 agilent24 = Agilent_34400A.Agilent_34400A(name='dmm1',address="GPIB::24")

qcodes\qcodes\instrument\base.py in __call__(cls, args, kwargs)
515 successfully.
516 """
--> 517 new_inst = super().__call__(args, kwargs)
518 is_abstract = new_inst._is_abstract()
519 if is_abstract:

qcodes\qcodes\instrument_drivers\agilent\Agilent_34400A.py in __init__(self, name, address, kwargs)
36 set_cmd=self._set_resolution,
37 label="Resolution",
---> 38 unit="V",
39 )
```

We tracked back what caused this error to this commit: https://github.com/QCoDeS/Qcodes/commit/4c1001a86912d3693852eb385b997a7eeb69e6c8.

Indeed, if we replace:
```python
        self.resolution = Parameter(
            "resolution",
            get_cmd="VOLT:DC:RES?",
            get_parser=float,
            set_cmd=self._set_resolution,
            label="Resolution",
            unit="V",
        )
```

by:
```python
        self.add_parameter('resolution',
                           get_cmd='VOLT:DC:RES?',
                           get_parser=float,
                           set_cmd=self._set_resolution,
                           label='Resolution',
                           unit='V')
```

The import works again.

**operating system**
Windows 10

If you are using a released version of qcodes (recommended):
**qcodes version**
'0.32.0.dev385+master.g465d59939c.dirty'

If you are using the development version of qcodes:
**qcodes branch**
master
**qcodes commit**
3a6571d3ee58669d11fc288a73743dd53a74e3c3



Discussion:

Resolving patch:
diff --git a/qcodes/instrument/parameter.py b/qcodes/instrument/parameter.py
--- a/qcodes/instrument/parameter.py
+++ b/qcodes/instrument/parameter.py
@@ -1247,8 +1247,15 @@ def __init__(
                 self.get_raw = (  # type: ignore[assignment]
                     lambda: self.cache.raw_value)
             else:
-                exec_str_ask = getattr(instrument, "ask", None) \
-                    if instrument else None
+                if isinstance(get_cmd, str) and instrument is None:
+                    raise TypeError(
+                        f"Cannot use a str get_cmd without "
+                        f"binding to an instrument. "
+                        f"Got: get_cmd {get_cmd} for parameter {name}"
+                    )
+
+                exec_str_ask = getattr(instrument, "ask", None) if instrument else None
+
                 self.get_raw = Command(arg_count=0,  # type: ignore[assignment]
                                        cmd=get_cmd,
                                        exec_str=exec_str_ask)
@@ -1263,10 +1270,19 @@ def __init__(
             if set_cmd is None:
                 self.set_raw: Callable[..., Any] = lambda x: x
             else:
-                exec_str_write = getattr(instrument, "write", None) \
-                    if instrument else None
-                self.set_raw = Command(arg_count=1, cmd=set_cmd,
-                                       exec_str=exec_str_write)
+                if isinstance(set_cmd, str) and instrument is None:
+                    raise TypeError(
+                        f"Cannot use a str set_cmd without "
+                        f"binding to an instrument. "
+                        f"Got: set_cmd {set_cmd} for parameter {name}"
+                    )
+
+                exec_str_write = (
+                    getattr(instrument, "write", None) if instrument else None
+                )
+                self.set_raw = Command(
+                    arg_count=1, cmd=set_cmd, exec_str=exec_str_write
+                )
             self._settable = True
             self.set = self._wrap_set(self.set_raw)
 
diff --git a/qcodes/instrument_drivers/Keysight/Infiniium.py b/qcodes/instrument_drivers/Keysight/Infiniium.py
--- a/qcodes/instrument_drivers/Keysight/Infiniium.py
+++ b/qcodes/instrument_drivers/Keysight/Infiniium.py
@@ -829,6 +829,7 @@ def __init__(
         # Trigger
         self.trigger_mode = Parameter(
             name="trigger_mode",
+            instrument=self,
             label="Trigger mode",
             get_cmd=":TRIG:MODE?",
         )
diff --git a/qcodes/instrument_drivers/agilent/Agilent_34400A.py b/qcodes/instrument_drivers/agilent/Agilent_34400A.py
--- a/qcodes/instrument_drivers/agilent/Agilent_34400A.py
+++ b/qcodes/instrument_drivers/agilent/Agilent_34400A.py
@@ -31,6 +31,7 @@ def __init__(self, name: str, address: str, **kwargs: Any) -> None:
 
         self.resolution = Parameter(
             "resolution",
+            instrument=self,
             get_cmd="VOLT:DC:RES?",
             get_parser=float,
             set_cmd=self._set_resolution,


Test patch:
diff --git a/qcodes/tests/parameter/test_parameter_basics.py b/qcodes/tests/parameter/test_parameter_basics.py
--- a/qcodes/tests/parameter/test_parameter_basics.py
+++ b/qcodes/tests/parameter/test_parameter_basics.py
@@ -8,7 +8,7 @@
     GettableParam,
     VirtualParameter,
     blank_instruments,
-    named_instrument
+    named_instrument,
 )
 
 
@@ -189,3 +189,17 @@ def test_underlying_instrument_for_virtual_parameter():
     vp = VirtualParameter('test_param', param=p)
 
     assert vp.underlying_instrument is named_instrument
+
+
+def test_get_cmd_str_no_instrument_raises():
+    with pytest.raises(
+        TypeError, match="Cannot use a str get_cmd without binding to an instrument."
+    ):
+        Parameter(name="test", instrument=None, get_cmd="get_me")
+
+
+def test_set_cmd_str_no_instrument_raises():
+    with pytest.raises(
+        TypeError, match="Cannot use a str set_cmd without binding to an instrument."
+    ):
+        Parameter(name="test", instrument=None, set_cmd="set_me")