Repository: PennyLaneAI/pennylane
Pull request #3454 (2022-12-01 16:55:55)

Remove with_tape from pennylane source code
**Context:**
`AnnotatedQueue` + `QuantumScript` is gradually replacing `QuantumTape`. Using `with QuantumTape()` in any way should not be done anymore, so this PR makes the first step in deprecating that.

**Description of the Change:**
- Adds the static `QuantumScript.from_queue(queue: AnnotatedQueue) -> QuantumScript` method to facilitate script construction
- Replaces all instances of `with .*[tT]ape.*:` in one of two ways:

If the enclosed code was miscellaneous stuff needing queuing:
```python
# old way
with qml.tape.QuantumTape() as tape:
    # some code here

# new way
with qml.queuing.AnnotatedQueue() as q:
    # same code here
qscript = qml.tape.QuantumScript.from_queue(q)
```
If the tape just wrapped a function call:
```python
# old way
with qml.tape.QuantumTape() as tape:
    func(*args, **kwargs)

# new way
qscript = qml.tape.make_qscript(func)(*args, **kwargs)
```
- `QNode.tape` is now a `QuantumScript`, not a `QuantumTape`.
- note that docstrings, variable names, and tests have not been updated unless necessary
  - each of these three categories will be addressed in their own follow-up PR

**Benefits:**
`QuantumTape` is one step closer to being deprecated

**Possible Drawbacks:**
- Users might expect `type(some_object)` to be a `QuantumTape`, but now many of those things will be of type `QuantumScript`. It has no functional difference, but it may cause confusion.

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
timmysilv: [sc-26068]
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3454](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (b1f8a08) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/0d8a73b7a072eab2ab03615a26d773b4b9fb6063?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (0d8a73b) will **increase** coverage by `0.01%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master    #3454      +/-   ##
==========================================
+ Coverage   99.68%   99.69%   +0.01%     
==========================================
  Files         300      300              
  Lines       26918    26894      -24     
==========================================
- Hits        26833    26812      -21     
+ Misses         85       82       -3     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/\_device.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL19kZXZpY2UucHk=) | `98.15% <100.00%> (-0.02%)` | :arrow_down: |
| [pennylane/drawer/tape\_text.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RyYXdlci90YXBlX3RleHQucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/measurements.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9tZWFzdXJlbWVudHMucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/operation.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wZXJhdGlvbi5weQ==) | `97.37% <100.00%> (+0.35%)` | :arrow_up: |
| [pennylane/ops/functions/map\_wires.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvbWFwX3dpcmVzLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/ops/op\_math/adjoint\_constructor.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9vcF9tYXRoL2Fkam9pbnRfY29uc3RydWN0b3IucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/optimize/qnspsa.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wdGltaXplL3Fuc3BzYS5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/qinfo/transforms.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3FpbmZvL3RyYW5zZm9ybXMucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/qnode.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3Fub2RlLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/shadows/transforms.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3NoYWRvd3MvdHJhbnNmb3Jtcy5weQ==) | `100.00% <100.00%> (ø)` | |
| ... and [10 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3454/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index ea511a979..59a54ebd9 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -94,7 +94,7 @@
   [#3279](https://github.com/PennyLaneAI/pennylane/pull/3279)
 
 * New `pauli_sentence()` function which takes native `Operator` or `Hamiltonian`
-  instances representing a linear combination of Pauli words and returns 
+  instances representing a linear combination of Pauli words and returns
   the equivalent `PauliSentence`.
   [(#3389)](https://github.com/PennyLaneAI/pennylane/pull/3389)
 
@@ -111,7 +111,7 @@
   1.23 * X(0) @ Z(1)
   ```
 
-* Added two new methods `operation()`, `hamiltonian()` for both `PauliSentence` and `PauliWord` classes to generate an equivalent PennyLane 
+* Added two new methods `operation()`, `hamiltonian()` for both `PauliSentence` and `PauliWord` classes to generate an equivalent PennyLane
   `Operation` or `Hamiltonian` instance from a `PauliSentence` or `PauliWord` one.
   [(#3391)](https://github.com/PennyLaneAI/pennylane/pull/3391)
 
@@ -245,6 +245,9 @@
   Replaces `qml.transforms.make_tape` with `make_qscript`.
   [(#3429)](https://github.com/PennyLaneAI/pennylane/pull/3429)
 
+* Replace (almost) all instances of `with QuantumTape()` with `QuantumScript` construction.
+  [(#3454)](https://github.com/PennyLaneAI/pennylane/pull/3454)
+
 <h4>Return types project</h4>
 
 * The autograd interface for the new return types now supports devices with shot vectors.
diff --git a/pennylane/_device.py b/pennylane/_device.py
index d80ef6f00..89d1ad015 100644
--- a/pennylane/_device.py
+++ b/pennylane/_device.py
@@ -224,9 +224,7 @@ class Device(abc.ABC):
     @property
     def analytic(self):
         """Whether shots is None or not. Kept for backwards compatability."""
-        if self._shots is None:
-            return True
-        return False
+        return self._shots is None
 
     @property
     def wires(self):
@@ -605,7 +603,7 @@ class Device(abc.ABC):
         function accepts a queuable object (including a PennyLane operation
         and observable) and returns ``True`` if supported by the device."""
         return qml.BooleanFn(
-            lambda obj: not isinstance(obj, qml.tape.QuantumTape)
+            lambda obj: not isinstance(obj, qml.tape.QuantumScript)
             and self.supports_operation(obj.name)
         )
 
diff --git a/pennylane/drawer/tape_text.py b/pennylane/drawer/tape_text.py
index 76fa25f81..6d9a50c59 100644
--- a/pennylane/drawer/tape_text.py
+++ b/pennylane/drawer/tape_text.py
@@ -310,7 +310,7 @@ def tape_text(
             layer_str = [filler] * n_wires
 
             for op in layer:
-                if isinstance(op, qml.tape.QuantumTape):
+                if isinstance(op, qml.tape.QuantumScript):
                     layer_str = _add_grouping_symbols(op, layer_str, wire_map)
                     label = f"Tape:{cache['tape_offset']+len(tape_cache)}"
                     for w in op.wires:
diff --git a/pennylane/measurements/measurements.py b/pennylane/measurements/measurements.py
index 2fe8c4f00..973ab6984 100644
--- a/pennylane/measurements/measurements.py
+++ b/pennylane/measurements/measurements.py
@@ -383,7 +383,7 @@ class MeasurementProcess(ABC):
         rotation and a measurement in the computational basis.
 
         Returns:
-            .QuantumTape: a quantum tape containing the operations
+            .QuantumScript: a quantum script containing the operations
             required to diagonalize the observable
 
         **Example**
@@ -397,28 +397,28 @@ class MeasurementProcess(ABC):
 
         Expanding this out:
 
-        >>> tape = m.expand()
+        >>> qscript = m.expand()
 
-        We can see that the resulting tape has the qubit unitary applied,
+        We can see that the resulting script has the qubit unitary applied,
         and a measurement process with no observable, but the eigenvalues
         specified:
 
-        >>> print(tape.operations)
+        >>> print(qscript.operations)
         [QubitUnitary(array([[-0.89442719,  0.4472136 ],
               [ 0.4472136 ,  0.89442719]]), wires=['a'])]
-        >>> print(tape.measurements[0].eigvals())
+        >>> print(qscript.measurements[0].eigvals())
         [0. 5.]
-        >>> print(tape.measurements[0].obs)
+        >>> print(qscript.measurements[0].obs)
         None
         """
         if self.obs is None:
             raise qml.operation.DecompositionUndefinedError
 
-        with qml.tape.QuantumTape() as tape:
+        with qml.queuing.AnnotatedQueue() as q:
             self.obs.diagonalizing_gates()
             self.__class__(wires=self.obs.wires, eigvals=self.obs.eigvals())
 
-        return tape
+        return qml.tape.QuantumScript.from_queue(q)
 
     def queue(self, context=qml.QueuingManager):
         """Append the measurement process to an annotated queue."""
diff --git a/pennylane/operation.py b/pennylane/operation.py
index 3b8855a4d..b422d7dec 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -705,7 +705,7 @@ class Operator(abc.ABC):
                 return format(x)
 
         param_string = ",\n".join(_format(p) for p in params)
-        return op_label + f"\n({param_string})"
+        return f"{op_label}\n({param_string})"
 
     def __init__(self, *params, wires=None, do_queue=True, id=None):
         # pylint: disable=too-many-branches
@@ -1091,19 +1091,18 @@ class Operator(abc.ABC):
         if not self.has_decomposition:
             raise DecompositionUndefinedError
 
-        tape = qml.tape.QuantumTape(do_queue=False)
-
-        with tape:
-            if getattr(self, "inverse", False):
-                qml.adjoint(self.decomposition, lazy=False)()
-            else:
-                self.decomposition()
+        decomp_fn = (
+            qml.adjoint(self.decomposition, lazy=False)
+            if getattr(self, "inverse", False)
+            else self.decomposition
+        )
+        qscript = qml.tape.make_qscript(decomp_fn)()
 
         if not self.data:
             # original operation has no trainable parameters
-            tape.trainable_params = {}
+            qscript.trainable_params = {}
 
-        return tape
+        return qscript
 
     @property
     def arithmetic_depth(self) -> int:
@@ -1383,10 +1382,7 @@ class Operation(Operator):
     def eigvals(self):
         op_eigvals = super().eigvals()
 
-        if self.inverse:
-            return qml.math.conj(op_eigvals)
-
-        return op_eigvals
+        return qml.math.conj(op_eigvals) if self.inverse else op_eigvals
 
     @property
     def base_name(self):
@@ -1397,7 +1393,7 @@ class Operation(Operator):
     @property
     def name(self):
         """Name of the operator."""
-        return self._name + ".inv" if self.inverse else self._name
+        return f"{self._name}.inv" if self.inverse else self._name
 
     def label(self, decimals=None, base_label=None, cache=None):
         if self.inverse:
@@ -1537,10 +1533,11 @@ class Observable(Operator):
         if self.return_type is None:
             return temp
 
-        if self.return_type is qml.measurements.Probability:
-            return repr(self.return_type) + f"(wires={self.wires.tolist()})"
-
-        return repr(self.return_type) + "(" + temp + ")"
+        return (
+            f"{repr(self.return_type)}(wires={self.wires.tolist()})"
+            if self.return_type is qml.measurements.Probability
+            else f"{repr(self.return_type)}({temp})"
+        )
 
     def __matmul__(self, other):
         if isinstance(other, (Tensor, qml.Hamiltonian)):
@@ -1712,10 +1709,7 @@ class Tensor(Observable):
         return "@".join(ob.label(decimals=decimals) for ob in self.obs)
 
     def queue(self, context=QueuingManager, init=False):  # pylint: disable=arguments-differ
-        constituents = self.obs
-
-        if init:
-            constituents = self._args
+        constituents = self._args if init else self.obs
 
         for o in constituents:
 
@@ -1747,10 +1741,11 @@ class Tensor(Observable):
         if self.return_type is None:
             return s
 
-        if self.return_type is qml.measurements.Probability:
-            return repr(self.return_type) + f"(wires={self.wires.tolist()})"
-
-        return repr(self.return_type) + "(" + s + ")"
+        return (
+            f"{repr(self.return_type)}(wires={self.wires.tolist()})"
+            if self.return_type is qml.measurements.Probability
+            else f"{repr(self.return_type)}({s})"
+        )
 
     @property
     def name(self):
@@ -2049,11 +2044,7 @@ class Tensor(Observable):
         (8, 8)
         """
 
-        if wires is None:
-            wires = self.wires
-        else:
-            wires = Wires(wires)
-
+        wires = self.wires if wires is None else Wires(wires)
         list_of_sparse_ops = [eye(2, format="coo")] * len(wires)
 
         for o in self.obs:
@@ -2188,11 +2179,7 @@ class CV:
             for k, w in enumerate(wire_indices):
                 W[loc(w)] = U[loc(k)]
         elif U.ndim == 2:
-            if isinstance(self, Observable):
-                W = np.zeros((dim, dim))
-            else:
-                W = np.eye(dim)
-
+            W = np.zeros((dim, dim)) if isinstance(self, Observable) else np.eye(dim)
             W[0, 0] = U[0, 0]
 
             for k1, w1 in enumerate(wire_indices):
@@ -2447,7 +2434,7 @@ def operation_derivative(operation) -> np.ndarray:
 @qml.BooleanFn
 def not_tape(obj):
     """Returns ``True`` if the object is not a quantum tape"""
-    return isinstance(obj, qml.tape.QuantumTape)
+    return isinstance(obj, qml.tape.QuantumScript)
 
 
 @qml.BooleanFn
@@ -2523,7 +2510,4 @@ def gen_is_multi_term_hamiltonian(obj):
     except (AttributeError, OperatorPropertyUndefined, GeneratorUndefinedError):
         return False
 
-    if isinstance(o, qml.Hamiltonian):
-        if len(o.coeffs) > 1:
-            return True
-    return False
+    return isinstance(o, qml.Hamiltonian) and len(o.coeffs) > 1
diff --git a/pennylane/ops/functions/map_wires.py b/pennylane/ops/functions/map_wires.py
index a223dc190..3c7953dc0 100644
--- a/pennylane/ops/functions/map_wires.py
+++ b/pennylane/ops/functions/map_wires.py
@@ -22,7 +22,7 @@ from pennylane.measurements import MeasurementProcess
 from pennylane.operation import Operator
 from pennylane.qnode import QNode
 from pennylane.queuing import QueuingManager
-from pennylane.tape import QuantumScript, QuantumTape
+from pennylane.tape import QuantumScript, make_qscript
 
 
 def map_wires(
@@ -111,12 +111,9 @@ def map_wires(
 
         @wraps(func)
         def qfunc(*args, **kwargs):
-            tape = QuantumTape()
-            with QueuingManager.stop_recording(), tape:
-                func(*args, **kwargs)
-
-            _ = [qml.map_wires(op, wire_map=wire_map, queue=True) for op in tape.operations]
-            m = tuple(qml.map_wires(m, wire_map=wire_map, queue=True) for m in tape.measurements)
+            qscript = make_qscript(func)(*args, **kwargs)
+            _ = [qml.map_wires(op, wire_map=wire_map, queue=True) for op in qscript.operations]
+            m = tuple(qml.map_wires(m, wire_map=wire_map, queue=True) for m in qscript.measurements)
             return m[0] if len(m) == 1 else m
 
         if isinstance(input, QNode):
diff --git a/pennylane/ops/op_math/adjoint_constructor.py b/pennylane/ops/op_math/adjoint_constructor.py
index f48f9662b..6fce063cb 100644
--- a/pennylane/ops/op_math/adjoint_constructor.py
+++ b/pennylane/ops/op_math/adjoint_constructor.py
@@ -18,7 +18,7 @@ from functools import wraps
 
 from pennylane.operation import Operator
 from pennylane.queuing import QueuingManager
-from pennylane.tape import QuantumTape
+from pennylane.tape import make_qscript
 
 from .adjoint_class import Adjoint
 
@@ -122,13 +122,11 @@ def adjoint(fn, lazy=True):
 
     @wraps(fn)
     def wrapper(*args, **kwargs):
-        with QueuingManager.stop_recording(), QuantumTape() as tape:
-            fn(*args, **kwargs)
-
+        qscript = make_qscript(fn)(*args, **kwargs)
         if lazy:
-            adjoint_ops = [Adjoint(op) for op in reversed(tape.operations)]
+            adjoint_ops = [Adjoint(op) for op in reversed(qscript.operations)]
         else:
-            adjoint_ops = [_single_op_eager(op) for op in reversed(tape.operations)]
+            adjoint_ops = [_single_op_eager(op) for op in reversed(qscript.operations)]
 
         return adjoint_ops[0] if len(adjoint_ops) == 1 else adjoint_ops
 
diff --git a/pennylane/optimize/qnspsa.py b/pennylane/optimize/qnspsa.py
index ae2265816..7b8368453 100644
--- a/pennylane/optimize/qnspsa.py
+++ b/pennylane/optimize/qnspsa.py
@@ -207,7 +207,7 @@ class QNSPSAOptimizer:
         all_grad_dirs = []
         all_metric_tapes = []
         all_tensor_dirs = []
-        for i in range(self.resamplings):
+        for _ in range(self.resamplings):
             # grad_tapes contains 2 tapes for the gradient estimation
             grad_tapes, grad_dirs = self._get_spsa_grad_tapes(cost, args, kwargs)
             # metric_tapes contains 4 tapes for tensor estimation
@@ -237,9 +237,7 @@ class QNSPSAOptimizer:
         self._update_tensor(tensor_avg)
         params_next = self._get_next_params(args, grad_avg)
 
-        if len(params_next) == 1:
-            return params_next[0]
-        return params_next
+        return params_next[0] if len(params_next) == 1 else params_next
 
     def _post_process_grad(self, grad_raw_results, grad_dirs):
         r"""Post process the gradient tape results to get the SPSA gradient estimation.
@@ -255,11 +253,10 @@ class QNSPSAOptimizer:
             the shape of the corresponding input parameter
         """
         loss_plus, loss_minus = grad_raw_results
-        grad = [
+        return [
             (loss_plus - loss_minus) / (2 * self.finite_diff_step) * grad_dir
             for grad_dir in grad_dirs
         ]
-        return grad
 
     def _post_process_tensor(self, tensor_raw_results, tensor_dirs):
         r"""Post process the corresponding tape results to get the metric tensor estimation.
@@ -284,7 +281,7 @@ class QNSPSAOptimizer:
             - tensor_raw_results[2][0]
             + tensor_raw_results[3][0]
         )
-        metric_tensor = (
+        return (
             -(
                 np.tensordot(tensor_dirs[0], tensor_dirs[1], axes=0)
                 + np.tensordot(tensor_dirs[1], tensor_dirs[0], axes=0)
@@ -292,7 +289,6 @@ class QNSPSAOptimizer:
             * tensor_finite_diff
             / (8 * self.finite_diff_step**2)
         )
-        return metric_tensor
 
     def _get_next_params(self, args, gradient):
         params = []
@@ -396,9 +392,11 @@ class QNSPSAOptimizer:
             args_list[2][index] = arg + self.finite_diff_step * (-dir1 + dir2)
             args_list[3][index] = arg - self.finite_diff_step * dir1
         dir_vecs = (np.concatenate(dir1_list), np.concatenate(dir2_list))
-        tapes = []
-        for args_finite_diff in args_list:
-            tapes.append(self._get_overlap_tape(cost, args, args_finite_diff, kwargs))
+        tapes = [
+            self._get_overlap_tape(cost, args, args_finite_diff, kwargs)
+            for args_finite_diff in args_list
+        ]
+
         return tapes, dir_vecs
 
     def _get_overlap_tape(self, cost, args1, args2, kwargs):
@@ -409,13 +407,8 @@ class QNSPSAOptimizer:
         op_forward = self._get_operations(cost, args1, kwargs)
         op_inv = self._get_operations(cost, args2, kwargs)
 
-        with qml.tape.QuantumTape() as tape:
-            for op in op_forward:
-                qml.apply(op)
-            for op in reversed(op_inv):
-                op.adjoint()
-            qml.probs(wires=cost.tape.wires.labels)
-        return tape
+        new_ops = op_forward + [op.adjoint() for op in reversed(op_inv)]
+        return qml.tape.QuantumScript(new_ops, [qml.probs(wires=cost.tape.wires.labels)])
 
     @staticmethod
     def _get_operations(cost, args, kwargs):
diff --git a/pennylane/qinfo/transforms.py b/pennylane/qinfo/transforms.py
index ff451ce95..4a5c9eac9 100644
--- a/pennylane/qinfo/transforms.py
+++ b/pennylane/qinfo/transforms.py
@@ -303,13 +303,7 @@ def _compute_cfim(p, dp):
 @batch_transform
 def _make_probs(tape, wires=None, post_processing_fn=None):
     """Ignores the return types of any qnode and creates a new one that outputs probabilities"""
-    if wires is None:
-        wires = tape.wires
-
-    with qml.tape.QuantumTape() as new_tape:
-        for op in tape.operations:
-            qml.apply(op)
-        qml.probs(wires=wires)
+    qscript = qml.tape.QuantumScript(tape.operations, [qml.probs(wires=wires or tape.wires)])
 
     if post_processing_fn is None:
 
@@ -320,7 +314,7 @@ def _make_probs(tape, wires=None, post_processing_fn=None):
 
             return qml.math.squeeze(qml.math.stack(res))
 
-    return [new_tape], post_processing_fn
+    return [qscript], post_processing_fn
 
 
 def classical_fisher(qnode, argnums=0):
diff --git a/pennylane/qnode.py b/pennylane/qnode.py
index 590f7b017..4528dcc4b 100644
--- a/pennylane/qnode.py
+++ b/pennylane/qnode.py
@@ -25,7 +25,7 @@ import autograd
 import pennylane as qml
 from pennylane import Device
 from pennylane.interfaces import INTERFACE_MAP, SUPPORTED_INTERFACES, set_shots
-from pennylane.tape import QuantumTape
+from pennylane.tape import QuantumScript, make_qscript
 
 
 class QNode:
@@ -35,7 +35,7 @@ class QNode:
     (corresponding to a :ref:`variational circuit <glossary_variational_circuit>`)
     and the computational device it is executed on.
 
-    The QNode calls the quantum function to construct a :class:`~.QuantumTape` instance representing
+    The QNode calls the quantum function to construct a :class:`~.QuantumScript` instance representing
     the quantum circuit.
 
     Args:
@@ -513,7 +513,7 @@ class QNode:
         )
 
     @property
-    def tape(self) -> QuantumTape:
+    def tape(self) -> QuantumScript:
         """The quantum tape"""
         return self._tape
 
@@ -522,11 +522,10 @@ class QNode:
     def construct(self, args, kwargs):
         """Call the quantum function with a tape context, ensuring the operations get queued."""
 
-        self._tape = qml.tape.QuantumTape()
-
-        with self.tape:
-            self._qfunc_output = self.func(*args, **kwargs)
-        self._tape._qfunc_output = self._qfunc_output
+        self._tape = make_qscript(self.func)(*args, **kwargs)
+        self._tape._queue_category = "_ops"
+        self._qfunc_output = self.tape._qfunc_output
+        qml.QueuingManager.append(self.tape)
 
         params = self.tape.get_parameters(trainable_only=False)
         self.tape.trainable_params = qml.math.get_trainable_indices(params)
diff --git a/pennylane/shadows/transforms.py b/pennylane/shadows/transforms.py
index 2237255c8..8cd93d44f 100644
--- a/pennylane/shadows/transforms.py
+++ b/pennylane/shadows/transforms.py
@@ -33,13 +33,14 @@ def _replace_obs(tape, obs, *args, **kwargs):
                 f"Tape measurement must be {qml.measurements.Shadow!r}, got {o.return_type!r}"
             )
 
-    with qml.tape.QuantumTape() as new_tape:
+    with qml.queuing.AnnotatedQueue() as q:
         # queue everything from the old tape except the measurement processes
         for op in tape.operations:
             qml.apply(op)
 
         # queue the new observable
         obs(*args, **kwargs)
+    qscript = qml.tape.QuantumScript.from_queue(q)
 
     def processing_fn(res):
         if qml.active_return():
@@ -48,7 +49,7 @@ def _replace_obs(tape, obs, *args, **kwargs):
 
         return qml.math.squeeze(qml.math.stack(res))
 
-    return [new_tape], processing_fn
+    return [qscript], processing_fn
 
 
 def shadow_expval(H, k=1):
@@ -96,7 +97,7 @@ def shadow_expval(H, k=1):
 
 def _shadow_state_diffable(wires):
     """Differentiable version of the shadow state transform"""
-    wires_list = [wires] if not isinstance(wires[0], list) else wires
+    wires_list = wires if isinstance(wires[0], list) else [wires]
 
     if any(len(w) >= 8 for w in wires_list):
         warnings.warn(
@@ -147,7 +148,7 @@ def _shadow_state_diffable(wires):
 
                 start += 4 ** len(w)
 
-            return states[0] if not isinstance(wires[0], list) else states
+            return states if isinstance(wires[0], list) else states[0]
 
         return wrapper
 
@@ -156,7 +157,7 @@ def _shadow_state_diffable(wires):
 
 def _shadow_state_undiffable(wires):
     """Non-differentiable version of the shadow state transform"""
-    wires_list = [wires] if not isinstance(wires[0], list) else wires
+    wires_list = wires if isinstance(wires[0], list) else [wires]
 
     def decorator(qnode):
         @wraps(qnode)
@@ -165,7 +166,7 @@ def _shadow_state_undiffable(wires):
             shadow = qml.shadows.ClassicalShadow(bits, recipes)
 
             states = [qml.math.mean(shadow.global_snapshots(wires=w), 0) for w in wires_list]
-            return states[0] if not isinstance(wires[0], list) else states
+            return states if isinstance(wires[0], list) else states[0]
 
         return wrapper
 
diff --git a/pennylane/tape/operation_recorder.py b/pennylane/tape/operation_recorder.py
index 16437a4d9..4ba5dae45 100644
--- a/pennylane/tape/operation_recorder.py
+++ b/pennylane/tape/operation_recorder.py
@@ -17,7 +17,7 @@ This module contains the :class:`OperationRecorder`.
 # pylint: disable=too-many-arguments
 from pennylane.queuing import QueuingManager
 
-from .tape import QuantumTape
+from .tape import QuantumTape, QuantumScript
 
 
 class OperationRecorder(QuantumTape):
@@ -58,7 +58,7 @@ class OperationRecorder(QuantumTape):
         for obj, info in self.items():
             QueuingManager.append(obj, **info)
 
-        new_tape = self.expand(depth=5, stop_at=lambda obj: not isinstance(obj, QuantumTape))
+        new_tape = self.expand(depth=5, stop_at=lambda obj: not isinstance(obj, QuantumScript))
         self.ops = new_tape.operations
         self.obs = new_tape.observables
 
diff --git a/pennylane/tape/qscript.py b/pennylane/tape/qscript.py
index 0fdf00d4d..dcdfab64a 100644
--- a/pennylane/tape/qscript.py
+++ b/pennylane/tape/qscript.py
@@ -1293,6 +1293,11 @@ class QuantumScript:
 
         return qasm_str
 
+    @staticmethod
+    def from_queue(queue):
+        """Construct a QuantumScript from an AnnotatedQueue."""
+        return QuantumScript(*process_queue(queue))
+
 
 def make_qscript(fn):
     """Returns a function that generates a qscript from a quantum function without any
@@ -1338,8 +1343,10 @@ def make_qscript(fn):
 
     def wrapper(*args, **kwargs):
         with qml.QueuingManager.stop_recording(), AnnotatedQueue() as q:
-            fn(*args, **kwargs)
+            result = fn(*args, **kwargs)
 
-        return QuantumScript(*process_queue(q))
+        qscript = QuantumScript.from_queue(q)
+        qscript._qfunc_output = result
+        return qscript
 
     return wrapper
diff --git a/pennylane/templates/subroutines/hilbert_schmidt.py b/pennylane/templates/subroutines/hilbert_schmidt.py
index d82fb3658..74fd3197b 100644
--- a/pennylane/templates/subroutines/hilbert_schmidt.py
+++ b/pennylane/templates/subroutines/hilbert_schmidt.py
@@ -100,7 +100,7 @@ class HilbertSchmidt(Operation):
 
         self._num_params = len(params)
 
-        if not isinstance(u_tape, qml.tape.QuantumTape):
+        if not isinstance(u_tape, qml.tape.QuantumScript):
             raise qml.QuantumFunctionError("The argument u_tape must be a QuantumTape.")
 
         u_wires = u_tape.wires
diff --git a/pennylane/transforms/adjoint_metric_tensor.py b/pennylane/transforms/adjoint_metric_tensor.py
index 9e3062540..55dcccf02 100644
--- a/pennylane/transforms/adjoint_metric_tensor.py
+++ b/pennylane/transforms/adjoint_metric_tensor.py
@@ -99,7 +99,7 @@ def adjoint_metric_tensor(circuit, device=None, hybrid=True):
           Note also that this makes the metric tensor strictly real-valued.
 
     Args:
-        circuit (.QuantumTape or .QNode): Circuit to compute the metric tensor of
+        circuit (.QuantumScript or .QNode): Circuit to compute the metric tensor of
         device (.Device): Device to use for the adjoint method
         hybrid (bool): Whether to take classical preprocessing into account. Ignored if
             ``circuit`` is a tape.
@@ -156,12 +156,12 @@ def adjoint_metric_tensor(circuit, device=None, hybrid=True):
     The drawback of the adjoint method is that it is only available on simulators and without
     shot simulations.
     """
-    if isinstance(circuit, qml.tape.QuantumTape):
+    if isinstance(circuit, qml.tape.QuantumScript):
         return _adjoint_metric_tensor_tape(circuit, device)
     if isinstance(circuit, (qml.QNode, qml.ExpvalCost)):
         return _adjoint_metric_tensor_qnode(circuit, device, hybrid)
 
-    raise qml.QuantumFunctionError("The passed object is not a QuantumTape or QNode.")
+    raise qml.QuantumFunctionError("The passed object is not a QuantumScript or QNode.")
 
 
 def _adjoint_metric_tensor_tape(tape, device):
diff --git a/pennylane/transforms/measurement_grouping.py b/pennylane/transforms/measurement_grouping.py
index 45152c163..751c4461d 100644
--- a/pennylane/transforms/measurement_grouping.py
+++ b/pennylane/transforms/measurement_grouping.py
@@ -85,13 +85,14 @@ def measurement_grouping(tape, obs_list, coeffs_list):
 
     for obs in obs_groupings:
 
-        with tape.__class__() as new_tape:
+        with qml.queuing.AnnotatedQueue() as q:
             for op in tape.operations:
                 op.queue()
 
             for o in obs:
                 qml.expval(o)
 
+        new_tape = qml.tape.QuantumScript.from_queue(q)
         new_tape = new_tape.expand(stop_at=lambda obj: True)
         tapes.append(new_tape)
 
diff --git a/pennylane/transforms/metric_tensor.py b/pennylane/transforms/metric_tensor.py
index cb0535a2d..e2f38e9f8 100644
--- a/pennylane/transforms/metric_tensor.py
+++ b/pennylane/transforms/metric_tensor.py
@@ -282,10 +282,7 @@ def _contract_metric_tensor_with_cjac(mt, cjac):
             for c in cjac
             if c is not None
         )
-        if len(metric_tensors) == 1:
-            return metric_tensors[0]
-
-        return metric_tensors
+        return metric_tensors[0] if len(metric_tensors) == 1 else metric_tensors
 
     is_square = cjac.shape == (1,) or (cjac.ndim == 2 and cjac.shape[0] == cjac.shape[1])
 
@@ -410,7 +407,7 @@ def _metric_tensor_cov_matrix(tape, diag_approx):
         # Create a quantum tape with all operations
         # prior to the parametrized layer, and the rotations
         # to measure in the basis of the parametrized layer generators.
-        with tape.__class__() as layer_tape:
+        with qml.queuing.AnnotatedQueue() as layer_q:
             for op in queue:
                 qml.apply(op)
 
@@ -419,6 +416,7 @@ def _metric_tensor_cov_matrix(tape, diag_approx):
 
             qml.probs(wires=tape.wires)
 
+        layer_tape = qml.tape.QuantumScript.from_queue(layer_q)
         metric_tensor_tapes.append(layer_tape)
 
     def processing_fn(probs):
@@ -482,12 +480,11 @@ def _get_gen_op(op, allow_nonunitary, aux_wire):
         ) from e
 
 
-def _get_first_term_tapes(tape, layer_i, layer_j, allow_nonunitary, aux_wire):
+def _get_first_term_tapes(layer_i, layer_j, allow_nonunitary, aux_wire):
     r"""Obtain the tapes for the first term of all tensor entries
     belonging to an off-diagonal block.
 
     Args:
-        tape (pennylane.tape.QuantumTape): Tape that is being transformed
         layer_i (list): The first layer of parametrized ops, of the format of
             the layers generated by ``iterate_parametrized_layers``
         layer_j (list): The second layer of parametrized ops
@@ -497,10 +494,10 @@ def _get_first_term_tapes(tape, layer_i, layer_j, allow_nonunitary, aux_wire):
             control the controlled-generator operations
 
     Returns:
-        list[pennylane.tape.QuantumTape]: Transformed tapes that compute the
+        list[pennylane.tape.QuantumScript]: Transformed qscripts that compute the
             first term of the metric tensor for the off-diagonal block belonging
             to the input layers
-        list[tuple[int]]: 2-tuple indices assigning the tapes to metric tensor
+        list[tuple[int]]: 2-tuple indices assigning the qscripts to metric tensor
             entries
     """
 
@@ -518,7 +515,7 @@ def _get_first_term_tapes(tape, layer_i, layer_j, allow_nonunitary, aux_wire):
         for diffed_op_j, par_idx_j in zip(layer_j.ops, layer_j.param_inds):
             gen_op_j = _get_gen_op(diffed_op_j, allow_nonunitary, aux_wire)
 
-            with tape.__class__() as new_tape:
+            with qml.queuing.AnnotatedQueue() as q:
                 # Initialize auxiliary wire
                 qml.Hadamard(wires=aux_wire)
                 # Apply backward cone of first layer
@@ -534,7 +531,7 @@ def _get_first_term_tapes(tape, layer_i, layer_j, allow_nonunitary, aux_wire):
                 # Measure X on auxiliary wire
                 qml.expval(qml.PauliX(aux_wire))
 
-            tapes.append(new_tape)
+            tapes.append(qml.tape.QuantumScript.from_queue(q))
             # Memorize to which metric entry this tape belongs
             ids.append((par_idx_i, par_idx_j))
 
@@ -594,7 +591,7 @@ def _metric_tensor_hadamard(tape, allow_nonunitary, aux_wire, device_wires):
         block_sizes.append(len(layer_i.param_inds))
 
         for layer_j in layers[idx_i + 1 :]:
-            _tapes, _ids = _get_first_term_tapes(tape, layer_i, layer_j, allow_nonunitary, aux_wire)
+            _tapes, _ids = _get_first_term_tapes(layer_i, layer_j, allow_nonunitary, aux_wire)
             first_term_tapes.extend(_tapes)
             ids.extend(_ids)
 
diff --git a/pennylane/transforms/mitigate.py b/pennylane/transforms/mitigate.py
index 49a727a57..b22a0d790 100644
--- a/pennylane/transforms/mitigate.py
+++ b/pennylane/transforms/mitigate.py
@@ -18,7 +18,8 @@ from typing import Any, Dict, Optional, Sequence, Union
 
 from pennylane import QNode, apply, adjoint
 from pennylane.math import mean, shape, round
-from pennylane.tape import QuantumTape
+from pennylane.queuing import AnnotatedQueue
+from pennylane.tape import QuantumTape, QuantumScript
 from pennylane.transforms import batch_transform
 
 import pennylane as qml
@@ -40,7 +41,7 @@ def fold_global(circuit, scale_factor):
         scale_factor (float): Scale factor :math:`\lambda` determining :math:`n` and :math:`s`
 
     Returns:
-        QuantumTape: Folded circuit
+        QuantumScript: Folded circuit
 
     .. seealso:: :func:`~.pennylane.transforms.mitigate_with_zne`; This function is analogous to the implementation in ``mitiq``  `mitiq.zne.scaling.fold_global <https://mitiq.readthedocs.io/en/v.0.1a2/apidoc.html?highlight=global_folding#mitiq.zne.scaling.fold_global>`_.
 
@@ -195,7 +196,7 @@ def fold_global_tape(circuit, scale_factor):
         scale_factor (float): Scale factor :math:`\lambda` determining :math:`n` and :math:`s`
 
     Returns:
-        QuantumTape: Folded circuit
+        QuantumScript: Folded circuit
 
     """
 
@@ -216,7 +217,7 @@ def fold_global_tape(circuit, scale_factor):
     num_to_fold = int(round(fraction_scale * n_ops / 2))
 
     # Create new_circuit from folded list
-    with QuantumTape() as new_circuit:
+    with AnnotatedQueue() as new_circuit_q:
         # Original U
         for op in base_ops:
             qfunc(op)
@@ -240,7 +241,7 @@ def fold_global_tape(circuit, scale_factor):
         for meas in circuit.measurements:
             apply(meas)
 
-    return new_circuit
+    return QuantumScript.from_queue(new_circuit_q)
 
 
 # TODO: make this a pennylane.math function
@@ -503,14 +504,15 @@ def mitigate_with_zne(
     if isinstance(folding, qml.batch_transform):
         folding = fold_global_tape
 
-    tape = circuit.expand(stop_at=lambda op: not isinstance(op, QuantumTape))
+    tape = circuit.expand(stop_at=lambda op: not isinstance(op, QuantumScript))
 
-    with QuantumTape() as tape_removed:
+    with AnnotatedQueue() as q_removed:
         for op in tape._ops:
             apply(op)
+    script_removed = QuantumScript.from_queue(q_removed)
 
     tapes = [
-        [folding(tape_removed, s, **folding_kwargs) for _ in range(reps_per_factor)]
+        [folding(script_removed, s, **folding_kwargs) for _ in range(reps_per_factor)]
         for s in scale_factors
     ]
 
@@ -520,11 +522,11 @@ def mitigate_with_zne(
 
     for tape_ in tapes:
         # pylint: disable=expression-not-assigned
-        with QuantumTape() as t:
+        with AnnotatedQueue() as q:
             [apply(p) for p in tape._prep]
             [apply(op) for op in tape_.operations]
             [apply(m) for m in tape.measurements]
-        out_tapes.append(t)
+        out_tapes.append(QuantumScript.from_queue(q))
 
     def processing_fn(results):
         """Maps from input tape executions to an error-mitigated estimate"""
diff --git a/pennylane/transforms/optimization/pattern_matching.py b/pennylane/transforms/optimization/pattern_matching.py
index 54a64143f..0a5308970 100644
--- a/pennylane/transforms/optimization/pattern_matching.py
+++ b/pennylane/transforms/optimization/pattern_matching.py
@@ -23,7 +23,7 @@ import numpy as np
 import pennylane as qml
 from pennylane import adjoint
 from pennylane.ops.qubit.attributes import symmetric_over_all_wires
-from pennylane.tape import QuantumTape
+from pennylane.tape import QuantumTape, QuantumScript
 from pennylane.transforms import qfunc_transform
 from pennylane.transforms.commutation_dag import commutation_dag
 from pennylane.wires import Wires
@@ -178,7 +178,7 @@ def pattern_matching_optimization(tape: QuantumTape, pattern_tapes, custom_quant
 
     for pattern in pattern_tapes:
         # Check the validity of the pattern
-        if not isinstance(pattern, QuantumTape):
+        if not isinstance(pattern, QuantumScript):
             raise qml.QuantumFunctionError("The pattern is not a valid quantum tape.")
 
         # Check that it does not contain a measurement.
@@ -211,7 +211,7 @@ def pattern_matching_optimization(tape: QuantumTape, pattern_tapes, custom_quant
             # If some substitutions are possible, we create an optimized circuit.
             if substitution.substitution_list:
                 # Create a tape that does not affect the outside context.
-                with QuantumTape(do_queue=False) as tape_inside:
+                with qml.queuing.AnnotatedQueue() as q_inside:
                     # Loop over all possible substitutions
                     for group in substitution.substitution_list:
 
@@ -252,7 +252,8 @@ def pattern_matching_optimization(tape: QuantumTape, pattern_tapes, custom_quant
                         inst = copy.deepcopy(node.op)
                         qml.apply(inst)
 
-                tape = qml.map_wires(input=tape_inside, wire_map=inverse_wires_map)
+                qscript = QuantumScript.from_queue(q_inside)
+                tape = qml.map_wires(input=qscript, wire_map=inverse_wires_map)
 
     for op in tape.operations:
         qml.apply(op)
diff --git a/pennylane/transforms/qcut/montecarlo.py b/pennylane/transforms/qcut/montecarlo.py
index 2bbb1e77f..eca03bdb4 100644
--- a/pennylane/transforms/qcut/montecarlo.py
+++ b/pennylane/transforms/qcut/montecarlo.py
@@ -26,7 +26,8 @@ from networkx import MultiDiGraph
 import pennylane as qml
 from pennylane import numpy as np
 from pennylane.measurements import Sample
-from pennylane.tape import QuantumTape
+from pennylane.queuing import AnnotatedQueue
+from pennylane.tape import QuantumTape, QuantumScript
 from pennylane.wires import Wires
 
 from pennylane.transforms.batch_transform import batch_transform
@@ -676,7 +677,7 @@ def expand_fragment_tapes_mc(
     for tape in tapes:
         frag_config = []
         for shot in range(shots):
-            with qml.tape.QuantumTape() as new_tape:
+            with AnnotatedQueue() as q:
                 for op in tape.operations:
                     w = op.wires[0]
                     if isinstance(op, PrepareNode):
@@ -687,11 +688,11 @@ def expand_fragment_tapes_mc(
                 for meas in tape.measurements:
                     qml.apply(meas)
                 for op in tape.operations:
-                    meas_w = op.wires[0]
                     if isinstance(op, MeasureNode):
+                        meas_w = op.wires[0]
                         MC_MEASUREMENTS[meas_settings[op.id][shot]](meas_w)
 
-            frag_config.append(new_tape)
+            frag_config.append(QuantumScript.from_queue(q))
 
         all_configs.append(frag_config)
 
diff --git a/pennylane/transforms/qcut/tapes.py b/pennylane/transforms/qcut/tapes.py
index a6880809a..5eac43a0b 100644
--- a/pennylane/transforms/qcut/tapes.py
+++ b/pennylane/transforms/qcut/tapes.py
@@ -26,7 +26,8 @@ from pennylane import apply, expval
 from pennylane.pauli import string_to_pauli_word
 from pennylane.measurements import Expectation, MeasurementProcess, Sample
 from pennylane.ops.qubit.non_parametric_ops import WireCut
-from pennylane.tape import QuantumTape
+from pennylane.queuing import AnnotatedQueue
+from pennylane.tape import QuantumTape, QuantumScript
 from pennylane.wires import Wires
 from pennylane.operation import Operator, Tensor
 
@@ -105,9 +106,9 @@ def tape_to_graph(tape: QuantumTape) -> MultiDiGraph:
 
 
 # pylint: disable=protected-access
-def graph_to_tape(graph: MultiDiGraph) -> QuantumTape:
+def graph_to_tape(graph: MultiDiGraph) -> QuantumScript:
     """
-    Converts a directed multigraph to the corresponding :class:`~.QuantumTape`.
+    Converts a directed multigraph to the corresponding :class:`~.QuantumScript`.
 
     To account for the possibility of needing to perform mid-circuit measurements, if any operations
     follow a :class:`MeasureNode` operation on a given wire then these operations are mapped to a
@@ -122,7 +123,7 @@ def graph_to_tape(graph: MultiDiGraph) -> QuantumTape:
         graph (nx.MultiDiGraph): directed multigraph to be converted to a tape
 
     Returns:
-        QuantumTape: the quantum tape corresponding to the input graph
+        QuantumScript: the quantum script corresponding to the input graph
 
     **Example**
 
@@ -163,7 +164,7 @@ def graph_to_tape(graph: MultiDiGraph) -> QuantumTape:
     copy_meas = [copy.copy(op) for _, op in ordered_ops if isinstance(op, MeasurementProcess)]
     observables = []
 
-    with QuantumTape() as tape:
+    with AnnotatedQueue() as q:
         for op in copy_ops:
             op = qml.map_wires(op, wire_map=wire_map, queue=True)
             if isinstance(op, MeasureNode):
@@ -206,7 +207,7 @@ def graph_to_tape(graph: MultiDiGraph) -> QuantumTape:
                 else:
                     qml.expval(obs)
 
-    return tape
+    return QuantumScript.from_queue(q)
 
 
 def _add_operator_node(graph: MultiDiGraph, op: Operator, order: int, wire_latest_node: dict):
@@ -309,7 +310,7 @@ def expand_fragment_tape(
                 n: PREPARE_SETTINGS[s] for n, s in zip(prepare_nodes, prepare_settings)
             }
 
-            with QuantumTape() as tape_:
+            with AnnotatedQueue() as q:
                 for op in tape.operations:
                     if isinstance(op, PrepareNode):
                         w = op.wires[0]
@@ -322,7 +323,7 @@ def expand_fragment_tape(
                 for meas in measurements:
                     apply(meas)
 
-                tapes.append(tape_)
+            tapes.append(QuantumScript.from_queue(q))
 
     return tapes, prepare_nodes, measure_nodes
 
diff --git a/pennylane/transforms/qfunc_transforms.py b/pennylane/transforms/qfunc_transforms.py
index b49334e6a..ba42e6fc7 100644
--- a/pennylane/transforms/qfunc_transforms.py
+++ b/pennylane/transforms/qfunc_transforms.py
@@ -173,6 +173,8 @@ def _create_qfunc_internal_wrapper(fn, tape_transform, transform_args, transform
             f"The qfunc to transform, {fn}, does not appear "
             "to be a valid Python function or callable."
         )
+    if isinstance(fn, qml.QNode):
+        raise ValueError("QNodes cannot be declared as qfunc transforms.")
 
     @functools.wraps(fn)
     def internal_wrapper(*args, **kwargs):


Test patch:
diff --git a/tests/tape/test_qscript.py b/tests/tape/test_qscript.py
index 5d5156601..0ff233922 100644
--- a/tests/tape/test_qscript.py
+++ b/tests/tape/test_qscript.py
@@ -782,3 +782,15 @@ class TestMakeQscript:
         qscript = qml.tape.make_qscript(qfunc)()
         assert len(qscript.operations) == 1
         assert qscript.operations[0].name == "PauliX"
+
+
+def test_from_queue():
+    """Test that QuantumScript.from_queue works correctly."""
+    with qml.queuing.AnnotatedQueue() as q:
+        op = qml.PauliX(0)
+        with qml.QueuingManager.stop_recording():
+            qml.Hadamard(0)
+        qml.expval(qml.PauliZ(0))
+    qscript = QuantumScript.from_queue(q)
+    assert qscript.operations == [op]
+    assert len(qscript.measurements) == 1
diff --git a/tests/templates/test_subroutines/test_interferometer.py b/tests/templates/test_subroutines/test_interferometer.py
index 95a0a0584..fafbe6aa3 100644
--- a/tests/templates/test_subroutines/test_interferometer.py
+++ b/tests/templates/test_subroutines/test_interferometer.py
@@ -70,19 +70,19 @@ class TestInterferometer:
         )
 
         for rec in [op_rect.expand(), op_tria.expand()]:
-            assert len(rec.queue) == 4
+            assert len(rec.operations) == 4
 
-            assert isinstance(rec.queue[0], qml.Rotation)
-            assert rec.queue[0].parameters == phi
+            assert isinstance(rec.operations[0], qml.Rotation)
+            assert rec.operations[0].parameters == phi
 
-            assert isinstance(rec.queue[1], qml.Beamsplitter)
-            assert rec.queue[1].parameters == [theta[0], 0]
+            assert isinstance(rec.operations[1], qml.Beamsplitter)
+            assert rec.operations[1].parameters == [theta[0], 0]
 
-            assert isinstance(rec.queue[2], qml.Rotation)
-            assert rec.queue[2].parameters == [varphi[0]]
+            assert isinstance(rec.operations[2], qml.Rotation)
+            assert rec.operations[2].parameters == [varphi[0]]
 
-            assert isinstance(rec.queue[3], qml.Rotation)
-            assert rec.queue[3].parameters == [varphi[1]]
+            assert isinstance(rec.operations[3], qml.Rotation)
+            assert rec.operations[3].parameters == [varphi[1]]
 
     def test_one_mode(self, tol):
         """Test that a one mode interferometer correctly gives a rotation gate"""
@@ -91,9 +91,9 @@ class TestInterferometer:
         op = qml.Interferometer(theta=[], phi=[], varphi=varphi, wires=0)
         rec = op.expand()
 
-        assert len(rec.queue) == 1
-        assert isinstance(rec.queue[0], qml.Rotation)
-        assert np.allclose(rec.queue[0].parameters, varphi, atol=tol)
+        assert len(rec.operations) == 1
+        assert isinstance(rec.operations[0], qml.Rotation)
+        assert np.allclose(rec.operations[0].parameters, varphi, atol=tol)
 
     def test_two_mode_rect(self, tol):
         """Test that a two mode interferometer using the rectangular mesh
@@ -108,14 +108,14 @@ class TestInterferometer:
         op = qml.Interferometer(theta, phi, varphi, wires=wires)
         rec = op.expand()
 
-        isinstance(rec.queue[0], qml.Beamsplitter)
-        assert rec.queue[0].parameters == theta + phi
+        isinstance(rec.operations[0], qml.Beamsplitter)
+        assert rec.operations[0].parameters == theta + phi
 
-        assert isinstance(rec.queue[1], qml.Rotation)
-        assert rec.queue[1].parameters == [varphi[0]]
+        assert isinstance(rec.operations[1], qml.Rotation)
+        assert rec.operations[1].parameters == [varphi[0]]
 
-        assert isinstance(rec.queue[2], qml.Rotation)
-        assert rec.queue[2].parameters == [varphi[1]]
+        assert isinstance(rec.operations[2], qml.Rotation)
+        assert rec.operations[2].parameters == [varphi[1]]
 
     def test_two_mode_triangular(self, tol):
         """Test that a two mode interferometer using the triangular mesh
@@ -130,16 +130,16 @@ class TestInterferometer:
         op = qml.Interferometer(theta, phi, varphi, mesh="triangular", wires=wires)
         rec = op.expand()
 
-        assert len(rec.queue) == 3
+        assert len(rec.operations) == 3
 
-        assert isinstance(rec.queue[0], qml.Beamsplitter)
-        assert rec.queue[0].parameters == theta + phi
+        assert isinstance(rec.operations[0], qml.Beamsplitter)
+        assert rec.operations[0].parameters == theta + phi
 
-        assert isinstance(rec.queue[1], qml.Rotation)
-        assert rec.queue[1].parameters == [varphi[0]]
+        assert isinstance(rec.operations[1], qml.Rotation)
+        assert rec.operations[1].parameters == [varphi[0]]
 
-        assert isinstance(rec.queue[2], qml.Rotation)
-        assert rec.queue[2].parameters == [varphi[1]]
+        assert isinstance(rec.operations[2], qml.Rotation)
+        assert rec.operations[2].parameters == [varphi[1]]
 
     def test_three_mode(self, tol):
         """Test that a three mode interferometer using either mesh gives the correct gates"""
@@ -155,32 +155,32 @@ class TestInterferometer:
 
         # Test rectangular mesh
         rec = op_rect.expand()
-        assert len(rec.queue) == 6
+        assert len(rec.operations) == 6
 
         expected_bs_wires = [[0, 1], [1, 2], [0, 1]]
 
-        for idx, op in enumerate(rec.queue[:3]):
+        for idx, op in enumerate(rec.operations[:3]):
             assert isinstance(op, qml.Beamsplitter)
             assert op.parameters == [theta[idx], phi[idx]]
             assert op.wires == Wires(expected_bs_wires[idx])
 
-        for idx, op in enumerate(rec.queue[3:]):
+        for idx, op in enumerate(rec.operations[3:]):
             assert isinstance(op, qml.Rotation)
             assert op.parameters == [varphi[idx]]
             assert op.wires == Wires([idx])
 
         # Test triangular mesh
         rec = op_tria.expand()
-        assert len(rec.queue) == 6
+        assert len(rec.operations) == 6
 
         expected_bs_wires = [[1, 2], [0, 1], [1, 2]]
 
-        for idx, op in enumerate(rec.queue[:3]):
+        for idx, op in enumerate(rec.operations[:3]):
             assert isinstance(op, qml.Beamsplitter)
             assert op.parameters == [theta[idx], phi[idx]]
             assert op.wires == Wires(expected_bs_wires[idx])
 
-        for idx, op in enumerate(rec.queue[3:]):
+        for idx, op in enumerate(rec.operations[3:]):
             assert isinstance(op, qml.Rotation)
             assert op.parameters == [varphi[idx]]
             assert op.wires == Wires([idx])
@@ -197,16 +197,16 @@ class TestInterferometer:
         op = qml.Interferometer(theta, phi, varphi, wires=wires)
         rec = op.expand()
 
-        assert len(rec.queue) == 10
+        assert len(rec.operations) == 10
 
         expected_bs_wires = [[0, 1], [2, 3], [1, 2], [0, 1], [2, 3], [1, 2]]
 
-        for idx, op in enumerate(rec.queue[:6]):
+        for idx, op in enumerate(rec.operations[:6]):
             assert isinstance(op, qml.Beamsplitter)
             assert op.parameters == [theta[idx], phi[idx]]
             assert op.wires == Wires(expected_bs_wires[idx])
 
-        for idx, op in enumerate(rec.queue[6:]):
+        for idx, op in enumerate(rec.operations[6:]):
             assert isinstance(op, qml.Rotation)
             assert op.parameters == [varphi[idx]]
             assert op.wires == Wires([idx])
@@ -223,16 +223,16 @@ class TestInterferometer:
         op = qml.Interferometer(theta, phi, varphi, wires=wires, mesh="triangular")
         rec = op.expand()
 
-        assert len(rec.queue) == 10
+        assert len(rec.operations) == 10
 
         expected_bs_wires = [[2, 3], [1, 2], [0, 1], [2, 3], [1, 2], [2, 3]]
 
-        for idx, op in enumerate(rec.queue[:6]):
+        for idx, op in enumerate(rec.operations[:6]):
             assert isinstance(op, qml.Beamsplitter)
             assert op.parameters == [theta[idx], phi[idx]]
             assert op.wires == Wires(expected_bs_wires[idx])
 
-        for idx, op in enumerate(rec.queue[6:]):
+        for idx, op in enumerate(rec.operations[6:]):
             assert isinstance(op, qml.Rotation)
             assert op.parameters == [varphi[idx]]
             assert op.wires == Wires([idx])
diff --git a/tests/templates/test_subroutines/test_qpe.py b/tests/templates/test_subroutines/test_qpe.py
index 6297269a1..4c6f63f1d 100644
--- a/tests/templates/test_subroutines/test_qpe.py
+++ b/tests/templates/test_subroutines/test_qpe.py
@@ -20,31 +20,32 @@ from scipy.stats import unitary_group
 class TestDecomposition:
     """Tests that the template defines the correct decomposition."""
 
-    def test_expected_tape(self):
-        """Tests if QuantumPhaseEstimation populates the tape as expected for a fixed example"""
+    def test_expected_qscript(self):
+        """Tests if QuantumPhaseEstimation populates the quantum script as expected for a fixed example"""
 
         m = qml.RX(0.3, wires=0).matrix()
 
         op = qml.QuantumPhaseEstimation(m, target_wires=[0], estimation_wires=[1, 2])
-        tape = op.expand()
+        qscript = op.expand()
 
-        with qml.tape.QuantumTape() as tape2:
+        with qml.queuing.AnnotatedQueue() as q:
             qml.Hadamard(1),
             qml.ControlledQubitUnitary(m @ m, control_wires=[1], wires=[0]),
             qml.Hadamard(2),
             qml.ControlledQubitUnitary(m, control_wires=[2], wires=[0]),
             qml.adjoint(qml.QFT(wires=[1, 2]))
+        qscript2 = qml.tape.QuantumScript.from_queue(q)
 
-        assert len(tape2.queue) == len(tape.queue)
+        assert len(qscript) == len(qscript2)
         # qml.equal doesn't work for Adjoint op yet, so we stop before we get to it.
-        for op1, op2 in zip(tape.queue[:-1], tape2.queue[:-1]):
+        for op1, op2 in zip(qscript[:-1], qscript2[:-1]):
             assert qml.equal(op1, op2)
 
-        assert isinstance(tape[-1], qml.ops.op_math.Adjoint)
-        assert qml.equal(tape[-1].base, qml.QFT(wires=(1, 2)))
+        assert isinstance(qscript[-1], qml.ops.op_math.Adjoint)
+        assert qml.equal(qscript[-1].base, qml.QFT(wires=(1, 2)))
 
-        assert np.allclose(tape.queue[1].matrix(), tape2.queue[1].matrix())
-        assert np.allclose(tape.queue[3].matrix(), tape2.queue[3].matrix())
+        assert np.allclose(qscript[1].matrix(), qscript[1].matrix())
+        assert np.allclose(qscript[3].matrix(), qscript[3].matrix())
 
     @pytest.mark.parametrize("phase", [2, 3, 6, np.pi])
     def test_phase_estimated(self, phase):
diff --git a/tests/test_qnode.py b/tests/test_qnode.py
index 0be000f26..ebf72def9 100644
--- a/tests/test_qnode.py
+++ b/tests/test_qnode.py
@@ -23,7 +23,7 @@ import pennylane as qml
 from pennylane import QNode
 from pennylane import numpy as pnp
 from pennylane import qnode
-from pennylane.tape import QuantumTape
+from pennylane.tape import QuantumTape, QuantumScript
 
 
 def dummyfunc():
@@ -513,7 +513,7 @@ class TestTapeConstruction:
 
         res = qn(x, y)
 
-        assert isinstance(qn.qtape, QuantumTape)
+        assert isinstance(qn.qtape, QuantumScript)
         assert len(qn.qtape.operations) == 3
         assert len(qn.qtape.observables) == 1
         assert qn.qtape.num_params == 2
@@ -719,7 +719,7 @@ class TestDecorator:
 
         res = func(x, y)
 
-        assert isinstance(func.qtape, QuantumTape)
+        assert isinstance(func.qtape, QuantumScript)
         assert len(func.qtape.operations) == 3
         assert len(func.qtape.observables) == 1
         assert func.qtape.num_params == 2
diff --git a/tests/transforms/test_op_transform.py b/tests/transforms/test_op_transform.py
index e29b28966..dc67f922a 100644
--- a/tests/transforms/test_op_transform.py
+++ b/tests/transforms/test_op_transform.py
@@ -436,7 +436,7 @@ class TestQFuncTransformIntegration:
 
         weights = np.array([0.1, 0.0, 0.0])
 
-        with pytest.raises(TypeError):
+        with pytest.raises(ValueError, match="QNodes cannot be declared as qfunc transforms"):
             circuit(weights)