Repository: PennyLaneAI/pennylane
Pull request #3434 (2022-11-29 09:40:41)

CMP #11.1: Make `MeasurementProcess.return_type` a property
- Remove `return_type` from `MeasurementProcess.__init__` and make it a property of the class.
- Make `MeasurementProcess` an ABC.
- Remove all direct instances of `MeasurementProcess` and change them by an instance of the corresponding class.

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.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3434](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (86fbb5c) into [classical-shadow-cmp](https://codecov.io/gh/PennyLaneAI/pennylane/commit/91276819fe6bb0563a56a52f95bd0d3a732639b6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (9127681) will **decrease** coverage by `0.02%`.
> The diff coverage is `100.00%`.

```diff
@@                   Coverage Diff                    @@
##           classical-shadow-cmp    #3434      +/-   ##
========================================================
- Coverage                 99.70%   99.67%   -0.03%     
========================================================
  Files                       299      299              
  Lines                     26674    27382     +708     
========================================================
+ Hits                      26594    27292     +698     
- Misses                       80       90      +10     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/circuit\_graph.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2NpcmN1aXRfZ3JhcGgucHk=) | `93.14% <100.00%> (+0.03%)` | :arrow_up: |
| [pennylane/gradients/parameter\_shift.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9wYXJhbWV0ZXJfc2hpZnQucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/gradients/parameter\_shift\_cv.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9wYXJhbWV0ZXJfc2hpZnRfY3YucHk=) | `99.59% <100.00%> (ø)` | |
| [pennylane/measurements/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/classical\_shadow.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9jbGFzc2ljYWxfc2hhZG93LnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/counts.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9jb3VudHMucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/expval.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9leHB2YWwucHk=) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/measurements.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9tZWFzdXJlbWVudHMucHk=) | `99.37% <100.00%> (-0.63%)` | :arrow_down: |
| [pennylane/measurements/mid\_measure.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9taWRfbWVhc3VyZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/measurements/mutual\_info.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21lYXN1cmVtZW50cy9tdXR1YWxfaW5mby5weQ==) | `100.00% <100.00%> (ø)` | |
| ... and [18 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3434/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 1c777204d..2f0c11793 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -340,6 +340,13 @@
 
   [#3421](https://github.com/PennyLaneAI/pennylane/pull/3421)
 
+* The `MeasurementProcess.return_type` argument has been removed from the `__init__` method. Now
+  it is a property of the class.
+  [#3434](https://github.com/PennyLaneAI/pennylane/pull/3434)
+
+* The `MeasurementProcess` class is now an abstract class.
+  [#3434](https://github.com/PennyLaneAI/pennylane/pull/3434)
+
 <h3>Deprecations</h3>
 
 Deprecations cycles are tracked at [doc/developement/deprecations.rst](https://docs.pennylane.ai/en/latest/development/deprecations.html).
diff --git a/pennylane/circuit_graph.py b/pennylane/circuit_graph.py
index e3d3b5daa..3ce6c7c9f 100644
--- a/pennylane/circuit_graph.py
+++ b/pennylane/circuit_graph.py
@@ -22,7 +22,7 @@ import numpy as np
 import retworkx as rx
 
 import pennylane as qml
-from pennylane.measurements import MeasurementProcess, Sample, State
+from pennylane.measurements import Sample, State, _State
 from pennylane.wires import Wires
 
 
@@ -121,7 +121,7 @@ class CircuitGraph:
                     # State measurements contain no wires by default, but wires are
                     # required for the circuit drawer, so we recreate the state
                     # measurement with all wires
-                    op = MeasurementProcess(State, wires=wires)
+                    op = _State(wires=wires)
 
                 elif op.return_type is Sample and op.wires == Wires([]):
                     # Sampling without specifying wires is treated as sampling all wires
diff --git a/pennylane/gradients/parameter_shift.py b/pennylane/gradients/parameter_shift.py
index ed0eb6272..bbb658b99 100644
--- a/pennylane/gradients/parameter_shift.py
+++ b/pennylane/gradients/parameter_shift.py
@@ -23,10 +23,10 @@ from functools import partial
 import numpy as np
 
 import pennylane as qml
-from pennylane.measurements import MutualInfo, State, VnEntropy
 from pennylane._device import _get_num_copies
+from pennylane.measurements import MutualInfo, State, VnEntropy
 
-from .finite_difference import finite_diff, _all_zero_grad_new, _no_trainable_grad_new
+from .finite_difference import _all_zero_grad_new, _no_trainable_grad_new, finite_diff
 from .general_shift_rules import (
     _iterate_shift_rule,
     frequencies_to_period,
@@ -974,9 +974,7 @@ def _var_param_shift_tuple(
     # Convert all variance measurements on the tape into expectation values
     for i in var_indices:
         obs = expval_tape._measurements[i].obs
-        expval_tape._measurements[i] = qml.measurements.MeasurementProcess(
-            qml.measurements.Expectation, obs=obs
-        )
+        expval_tape._measurements[i] = qml.expval(op=obs)
 
     # evaluate the analytic derivative of <A>
     pdA_tapes, pdA_fn = expval_param_shift(
@@ -1012,9 +1010,7 @@ def _var_param_shift_tuple(
             # We need to calculate d<A^2>/dp; to do so, we replace the
             # involutory observables A in the queue with A^2.
             obs = _square_observable(tape_with_obs_squared_expval._measurements[i].obs)
-            tape_with_obs_squared_expval._measurements[i] = qml.measurements.MeasurementProcess(
-                qml.measurements.Expectation, obs=obs
-            )
+            tape_with_obs_squared_expval._measurements[i] = qml.expval(op=obs)
 
         # Non-involutory observables are present; the partial derivative of <A^2>
         # may be non-zero. Here, we calculate the analytic derivatives of the <A^2>
@@ -1084,9 +1080,7 @@ def var_param_shift(
     # Convert all variance measurements on the tape into expectation values
     for i in var_idx:
         obs = expval_tape._measurements[i].obs
-        expval_tape._measurements[i] = qml.measurements.MeasurementProcess(
-            qml.measurements.Expectation, obs=obs
-        )
+        expval_tape._measurements[i] = qml.expval(op=obs)
 
     # evaluate the analytic derivative of <A>
     pdA_tapes, pdA_fn = expval_param_shift(
@@ -1124,9 +1118,7 @@ def var_param_shift(
             # We need to calculate d<A^2>/dp; to do so, we replace the
             # involutory observables A in the queue with A^2.
             obs = _square_observable(expval_sq_tape._measurements[i].obs)
-            expval_sq_tape._measurements[i] = qml.measurements.MeasurementProcess(
-                qml.measurements.Expectation, obs=obs
-            )
+            expval_sq_tape._measurements[i] = qml.expval(op=obs)
 
         # Non-involutory observables are present; the partial derivative of <A^2>
         # may be non-zero. Here, we calculate the analytic derivatives of the <A^2>
diff --git a/pennylane/gradients/parameter_shift_cv.py b/pennylane/gradients/parameter_shift_cv.py
index 0a94aad1c..53ce7e09f 100644
--- a/pennylane/gradients/parameter_shift_cv.py
+++ b/pennylane/gradients/parameter_shift_cv.py
@@ -24,14 +24,10 @@ import numpy as np
 
 import pennylane as qml
 
-from .gradient_transform import (
-    gradient_transform,
-    grad_method_validation,
-    choose_grad_methods,
-)
 from .finite_difference import finite_diff
-from .parameter_shift import expval_param_shift, _get_operation_recipe
-from .general_shift_rules import process_shifts, generate_shifted_tapes
+from .general_shift_rules import generate_shifted_tapes, process_shifts
+from .gradient_transform import choose_grad_methods, grad_method_validation, gradient_transform
+from .parameter_shift import _get_operation_recipe, expval_param_shift
 
 
 def _grad_method(tape, idx):
@@ -216,9 +212,7 @@ def var_param_shift(tape, dev_wires, argnum=None, shifts=None, gradient_recipes=
     # Convert all variance measurements on the tape into expectation values
     for i in var_idx:
         obs = expval_tape._measurements[i].obs
-        expval_tape._measurements[i] = qml.measurements.MeasurementProcess(
-            qml.measurements.Expectation, obs=obs
-        )
+        expval_tape._measurements[i] = qml.expval(op=obs)
 
     gradient_tapes = [expval_tape]
 
@@ -246,9 +240,7 @@ def var_param_shift(tape, dev_wires, argnum=None, shifts=None, gradient_recipes=
         # with itself, to get a square symmetric matrix representing
         # the square of the observable
         obs = qml.PolyXP(np.outer(A, A), wires=obs.wires)
-        expval_sq_tape._measurements[i] = qml.measurements.MeasurementProcess(
-            qml.measurements.Expectation, obs=obs
-        )
+        expval_sq_tape._measurements[i] = qml.expval(op=obs)
 
     # Non-involutory observables are present; the partial derivative of <A^2>
     # may be non-zero. Here, we calculate the analytic derivatives of the <A^2>
@@ -408,9 +400,7 @@ def second_order_param_shift(tape, dev_wires, argnum=None, shifts=None, gradient
 
             constants.append(constant)
 
-            g_tape._measurements[idx] = qml.measurements.MeasurementProcess(
-                qml.measurements.Expectation, _transform_observable(obs, Z, dev_wires)
-            )
+            g_tape._measurements[idx] = qml.expval(op=_transform_observable(obs, Z, dev_wires))
 
         if not any(i is None for i in constants):
             # Check if *all* transformed observables corresponds to a constant
diff --git a/pennylane/measurements/__init__.py b/pennylane/measurements/__init__.py
index 5bef0e3ba..4c81c51bc 100644
--- a/pennylane/measurements/__init__.py
+++ b/pennylane/measurements/__init__.py
@@ -14,20 +14,32 @@
 """
 This module contains measurements supported by PennyLane.
 """
-from .classical_shadow import ClassicalShadow, Shadow, ShadowExpval, classical_shadow, shadow_expval
-from .counts import AllCounts, Counts, counts
-from .expval import Expectation, expval
+from .classical_shadow import ClassicalShadow, _ShadowExpval, classical_shadow, shadow_expval
+from .counts import _Counts, counts
+from .expval import _Expectation, expval
 from .measurements import (
+    AllCounts,
+    Counts,
+    Expectation,
     MeasurementProcess,
     MeasurementShapeError,
+    MidMeasure,
+    MutualInfo,
     ObservableReturnTypes,
+    Probability,
+    Sample,
     SampleMeasurement,
+    Shadow,
+    ShadowExpval,
+    State,
     StateMeasurement,
+    Variance,
+    VnEntropy,
 )
-from .mid_measure import MeasurementValue, MeasurementValueError, MidMeasure, measure
-from .mutual_info import MutualInfo, mutual_info
-from .probs import Probability, probs
-from .sample import Sample, sample
-from .state import State, density_matrix, state
-from .var import Variance, var
-from .vn_entropy import VnEntropy, vn_entropy
+from .mid_measure import MeasurementValue, MeasurementValueError, measure
+from .mutual_info import _MutualInfo, mutual_info
+from .probs import _Probability, probs
+from .sample import _Sample, sample
+from .state import _State, density_matrix, state
+from .var import _Variance, var
+from .vn_entropy import _VnEntropy, vn_entropy
diff --git a/pennylane/measurements/classical_shadow.py b/pennylane/measurements/classical_shadow.py
index 3f584bad8..3f489c263 100644
--- a/pennylane/measurements/classical_shadow.py
+++ b/pennylane/measurements/classical_shadow.py
@@ -90,7 +90,7 @@ def shadow_expval(H, k=1, seed=None, seed_recipes=True):
             UserWarning,
         )
     seed = seed or np.random.randint(2**30)
-    return _ShadowExpval(ShadowExpval, H=H, seed=seed, k=k)
+    return _ShadowExpval(H=H, seed=seed, k=k)
 
 
 def classical_shadow(wires, seed=None, seed_recipes=True):
@@ -215,7 +215,7 @@ def classical_shadow(wires, seed=None, seed_recipes=True):
         )
     wires = Wires(wires)
     seed = seed or np.random.randint(2**30)
-    return ClassicalShadow(Shadow, wires=wires, seed=seed)
+    return ClassicalShadow(wires=wires, seed=seed)
 
 
 class ClassicalShadow(CustomMeasurement):
@@ -306,6 +306,10 @@ class ClassicalShadow(CustomMeasurement):
     def numeric_type(self):
         return int
 
+    @property
+    def return_type(self):
+        return Shadow
+
     def shape(self, device=None):
         # otherwise, the return type requires a device
         if device is None:
@@ -320,7 +324,6 @@ class ClassicalShadow(CustomMeasurement):
 
     def __copy__(self):
         return self.__class__(
-            self.return_type,
             obs=copy.copy(self.obs),
             seed=self.seed,
             wires=self._wires,
@@ -370,6 +373,10 @@ class _ShadowExpval(CustomMeasurement):
     def numeric_type(self):
         return float
 
+    @property
+    def return_type(self):
+        return ShadowExpval
+
     def shape(self, device=None):
         return (1,)
 
@@ -399,7 +406,6 @@ class _ShadowExpval(CustomMeasurement):
             [copy.copy(H) for H in self.H] if isinstance(self.H, Iterable) else copy.copy(self.H)
         )
         return self.__class__(
-            self.return_type,
             H=H_copy,
             k=self.k,
             seed=self.seed,
diff --git a/pennylane/measurements/counts.py b/pennylane/measurements/counts.py
index d725cceb0..e05b97135 100644
--- a/pennylane/measurements/counts.py
+++ b/pennylane/measurements/counts.py
@@ -24,7 +24,7 @@ import pennylane as qml
 from pennylane.operation import Observable
 from pennylane.wires import Wires
 
-from .measurements import AllCounts, Counts, ObservableReturnTypes, SampleMeasurement
+from .measurements import AllCounts, Counts, SampleMeasurement
 
 
 def counts(op=None, wires=None, all_outcomes=False):
@@ -140,11 +140,7 @@ def counts(op=None, wires=None, all_outcomes=False):
             )
         wires = Wires(wires)
 
-    # TODO: Remove this conditional branch when using `Counts.process` in devices
-    if all_outcomes:
-        return _Counts(AllCounts, obs=op, wires=wires, all_outcomes=all_outcomes)
-
-    return _Counts(Counts, obs=op, wires=wires, all_outcomes=all_outcomes)
+    return _Counts(obs=op, wires=wires, all_outcomes=all_outcomes)
 
 
 # TODO: Make public when removing the ObservableReturnTypes enum
@@ -153,7 +149,6 @@ class _Counts(SampleMeasurement):
 
     def __init__(
         self,
-        return_type: ObservableReturnTypes,
         obs: Union[Observable, None] = None,
         wires=None,
         eigvals=None,
@@ -161,7 +156,11 @@ class _Counts(SampleMeasurement):
         all_outcomes=False,
     ):
         self.all_outcomes = all_outcomes
-        super().__init__(return_type, obs, wires, eigvals, id)
+        super().__init__(obs, wires, eigvals, id)
+
+    @property
+    def return_type(self):
+        return AllCounts if self.all_outcomes else Counts
 
     def process_samples(
         self,
@@ -187,21 +186,19 @@ class _Counts(SampleMeasurement):
         return [self._samples_to_counts(bin_sample) for bin_sample in samples]
 
     def _samples_to_counts(self, samples):
-        """Groups the samples into a dictionary showing number of occurences for
+        """Groups the samples into a dictionary showing number of occurrences for
         each possible outcome.
 
-        The format of the dictionary depends on obs.return_type, which is set when
-        calling measurements.counts by setting the kwarg all_outcomes (bool). By default,
+        The format of the dictionary depends on the all_outcomes attribute. By default,
         the dictionary will only contain the observed outcomes. Optionally (all_outcomes=True)
         the dictionary will instead contain all possible outcomes, with a count of 0
         for those not observed. See example.
 
-
         Args:
             samples: samples in an array of dimension ``(shots,len(wires))``
 
         Returns:
-            dict: dictionary with format ``{'outcome': num_occurences}``, including all
+            dict: dictionary with format ``{'outcome': num_occurrences}``, including all
                 outcomes for the sampled observable
 
         **Example**
@@ -212,11 +209,11 @@ class _Counts(SampleMeasurement):
                     [1, 0]], requires_grad=True)
 
             By default, this will return:
-            >>> self._samples_to_counts(samples, obs, num_wires)
+            >>> self._samples_to_counts(samples)
             {'00': 2, '10': 1}
 
-            However, if obs.return_type is AllCounts, this will return:
-            >>> self._samples_to_counts(samples, obs, num_wires)
+            However, if ``all_outcomes=True``, this will return:
+            >>> self._samples_to_counts(samples)
             {'00': 2, '01': 0, '10': 1, '11': 0}
 
             The variable all_outcomes can be set when running measurements.counts, i.e.:
@@ -254,7 +251,6 @@ class _Counts(SampleMeasurement):
 
     def __copy__(self):
         return self.__class__(
-            self.return_type,
             obs=copy.copy(self.obs),
             eigvals=self._eigvals,
             wires=self._wires,
diff --git a/pennylane/measurements/expval.py b/pennylane/measurements/expval.py
index 03ed7ef98..6175d3630 100644
--- a/pennylane/measurements/expval.py
+++ b/pennylane/measurements/expval.py
@@ -56,13 +56,17 @@ def expval(op: Operator):
     if not op.is_hermitian:
         warnings.warn(f"{op.name} might not be hermitian.")
 
-    return _Expectation(Expectation, obs=op)
+    return _Expectation(obs=op)
 
 
 # TODO: Make public when removing the ObservableReturnTypes enum
 class _Expectation(SampleMeasurement, StateMeasurement):
     """Measurement process that computes the probability of each computational basis state."""
 
+    @property
+    def return_type(self):
+        return Expectation
+
     def process_samples(
         self,
         samples: Sequence[complex],
diff --git a/pennylane/measurements/measurements.py b/pennylane/measurements/measurements.py
index a540db0f5..d7955bf01 100644
--- a/pennylane/measurements/measurements.py
+++ b/pennylane/measurements/measurements.py
@@ -108,13 +108,11 @@ class MeasurementShapeError(ValueError):
     quantum tape."""
 
 
-class MeasurementProcess:
+class MeasurementProcess(ABC):
     """Represents a measurement process occurring at the end of a
     quantum variational circuit.
 
     Args:
-        return_type (.ObservableReturnTypes): The type of measurement process.
-            This includes ``Expectation``, ``Variance``, ``Sample``, ``State``, or ``Probability``.
         obs (.Observable): The observable that is to be measured as part of the
             measurement process. Not all measurement processes require observables (for
             example ``Probability``); this argument is optional.
@@ -130,13 +128,11 @@ class MeasurementProcess:
     # pylint: disable=too-many-arguments
     def __init__(
         self,
-        return_type: ObservableReturnTypes,
         obs: Union[Observable, None] = None,
         wires=None,
         eigvals=None,
         id=None,
     ):
-        self.return_type = return_type
         self.obs = obs
         self.id = id
 
@@ -153,21 +149,26 @@ class MeasurementProcess:
             self._eigvals = np.array(eigvals)
 
         # TODO: remove the following lines once devices
-        # have been refactored to accept and understand recieving
+        # have been refactored to accept and understand receiving
         # measurement processes rather than specific observables.
 
         # The following lines are only applicable for measurement processes
         # that do not have corresponding observables (e.g., Probability). We use
-        # them to 'trick' the device into thinking it has recieved an observable.
+        # them to 'trick' the device into thinking it has received an observable.
 
         # Below, we imitate an identity observable, so that the
-        # device undertakes no action upon recieving this observable.
+        # device undertakes no action upon receiving this observable.
         self.name = "Identity"
         self.data = []
 
         # Queue the measurement process
         self.queue()
 
+    @property
+    def return_type(self):
+        """Measurement return type."""
+        return None
+
     @property
     @functools.lru_cache()
     def numeric_type(self):
@@ -505,7 +506,6 @@ class MeasurementProcess:
 
     def __copy__(self):
         return self.__class__(
-            self.return_type,
             obs=copy.copy(self.obs),
             wires=self._wires,
             eigvals=self._eigvals,
@@ -620,7 +620,7 @@ class MeasurementProcess:
 
         with qml.tape.QuantumTape() as tape:
             self.obs.diagonalizing_gates()
-            MeasurementProcess(self.return_type, wires=self.obs.wires, eigvals=self.obs.eigvals())
+            self.__class__(wires=self.obs.wires, eigvals=self.obs.eigvals())
 
         return tape
 
@@ -671,10 +671,7 @@ class MeasurementProcess:
         Returns:
             .MeasurementProcess: A measurement process with a simplified observable.
         """
-        if self.obs is None:
-            return self
-
-        return MeasurementProcess(return_type=self.return_type, obs=self.obs.simplify())
+        return self if self.obs is None else self.__class__(obs=self.obs.simplify())
 
     def map_wires(self, wire_map: dict):
         """Returns a copy of the current measurement process with its wires changed according to
@@ -748,7 +745,7 @@ class MeasurementProcess:
         return Wires([ordered_obs_wire_lst[index] for index in permutation])
 
 
-class SampleMeasurement(MeasurementProcess, ABC):
+class SampleMeasurement(MeasurementProcess):
     """Sample-based measurement process."""
 
     @abstractmethod
@@ -772,7 +769,7 @@ class SampleMeasurement(MeasurementProcess, ABC):
         """
 
 
-class StateMeasurement(MeasurementProcess, ABC):
+class StateMeasurement(MeasurementProcess):
     """State-based measurement process."""
 
     @abstractmethod
@@ -786,7 +783,7 @@ class StateMeasurement(MeasurementProcess, ABC):
         """
 
 
-class CustomMeasurement(MeasurementProcess, ABC):
+class CustomMeasurement(MeasurementProcess):
     """Custom measurement process.
 
     Any class inheriting from this class should define its own ``process`` method, which takes a
diff --git a/pennylane/measurements/mid_measure.py b/pennylane/measurements/mid_measure.py
index c02873d48..76a03c53a 100644
--- a/pennylane/measurements/mid_measure.py
+++ b/pennylane/measurements/mid_measure.py
@@ -70,13 +70,21 @@ def measure(wires):  # TODO: Change name to mid_measure
 
     # Create a UUID and a map between MP and MV to support serialization
     measurement_id = str(uuid.uuid4())[:8]
-    MeasurementProcess(MidMeasure, wires=wire, id=measurement_id)
+    _MidMeasure(wires=wire, id=measurement_id)
     return MeasurementValue(measurement_id)
 
 
 T = TypeVar("T")
 
 
+class _MidMeasure(MeasurementProcess):
+    """Mid-circuit measurement."""
+
+    @property
+    def return_type(self):
+        return MidMeasure
+
+
 class MeasurementValueError(ValueError):
     """Error raised when an unknown measurement value is being used."""
 
diff --git a/pennylane/measurements/mutual_info.py b/pennylane/measurements/mutual_info.py
index 742c4ed8c..72c7165bb 100644
--- a/pennylane/measurements/mutual_info.py
+++ b/pennylane/measurements/mutual_info.py
@@ -22,7 +22,7 @@ import pennylane as qml
 from pennylane.operation import Operator
 from pennylane.wires import Wires
 
-from .measurements import MutualInfo, ObservableReturnTypes, StateMeasurement
+from .measurements import MutualInfo, StateMeasurement
 
 
 def mutual_info(wires0, wires1, log_base=None):
@@ -73,15 +73,15 @@ def mutual_info(wires0, wires1, log_base=None):
 
     .. seealso:: :func:`~.vn_entropy`, :func:`pennylane.qinfo.transforms.mutual_info` and :func:`pennylane.math.mutual_info`
     """
+    wires0 = qml.wires.Wires(wires0)
+    wires1 = qml.wires.Wires(wires1)
+
     # the subsystems cannot overlap
     if [wire for wire in wires0 if wire in wires1]:
         raise qml.QuantumFunctionError(
             "Subsystems for computing mutual information must not overlap."
         )
-
-    wires0 = qml.wires.Wires(wires0)
-    wires1 = qml.wires.Wires(wires1)
-    return _MutualInfo(MutualInfo, wires=[wires0, wires1], log_base=log_base)
+    return _MutualInfo(wires=[wires0, wires1], log_base=log_base)
 
 
 class _MutualInfo(StateMeasurement):
@@ -90,7 +90,6 @@ class _MutualInfo(StateMeasurement):
     # pylint: disable=too-many-arguments, unused-argument
     def __init__(
         self,
-        return_type: ObservableReturnTypes,
         obs: Operator = None,
         wires=None,
         eigvals=None,
@@ -98,7 +97,11 @@ class _MutualInfo(StateMeasurement):
         log_base=None,
     ):
         self.log_base = log_base
-        super().__init__(return_type=return_type, obs=obs, wires=wires, eigvals=eigvals, id=id)
+        super().__init__(obs=obs, wires=wires, eigvals=eigvals, id=id)
+
+    @property
+    def return_type(self):
+        return MutualInfo
 
     def process_state(self, state: Sequence[complex], wire_order: Wires):
         return qml.math.mutual_info(
@@ -111,7 +114,6 @@ class _MutualInfo(StateMeasurement):
 
     def __copy__(self):
         return self.__class__(
-            self.return_type,
             obs=copy.copy(self.obs),
             wires=self._wires,
             eigvals=self._eigvals,
diff --git a/pennylane/measurements/probs.py b/pennylane/measurements/probs.py
index 9ba4d1053..f79c6b5c8 100644
--- a/pennylane/measurements/probs.py
+++ b/pennylane/measurements/probs.py
@@ -82,7 +82,7 @@ def probs(wires=None, op=None):
 
     Args:
         wires (Sequence[int] or int): the wire the operation acts on
-        op (Observable): Observable (with a diagonalzing_gates attribute) that rotates
+        op (Observable): Observable (with a diagonalizing_gates attribute) that rotates
          the computational basis
     """
     # pylint: disable=protected-access
@@ -111,14 +111,18 @@ def probs(wires=None, op=None):
                 "Cannot specify the wires to probs if an observable is "
                 "provided. The wires for probs will be determined directly from the observable."
             )
-        return _Probability(Probability, wires=qml.wires.Wires(wires))
-    return _Probability(Probability, obs=op)
+        wires = qml.wires.Wires(wires)
+    return _Probability(obs=op, wires=wires)
 
 
 # TODO: Make public when removing the ObservableReturnTypes enum
 class _Probability(SampleMeasurement, StateMeasurement):
     """Measurement process that computes the probability of each computational basis state."""
 
+    @property
+    def return_type(self):
+        return Probability
+
     def process_samples(
         self,
         samples: Sequence[complex],
@@ -175,8 +179,8 @@ class _Probability(SampleMeasurement, StateMeasurement):
 
     @staticmethod
     def _count_samples(indices, batch_size, dim):
-        """Count the occurences of sampled indices and convert them to relative
-        counts in order to estimate their occurence probability."""
+        """Count the occurrences of sampled indices and convert them to relative
+        counts in order to estimate their occurrence probability."""
         num_bins, bin_size = indices.shape[-2:]
         if batch_size is None:
             prob = qml.math.zeros((dim, num_bins), dtype="float64")
@@ -201,7 +205,7 @@ class _Probability(SampleMeasurement, StateMeasurement):
 
     def marginal_prob(self, prob, wire_order, batch_size):
         r"""Return the marginal probability of the computational basis
-        states by summing the probabiliites on the non-specified wires.
+        states by summing the probabilities on the non-specified wires.
 
         If no wires are specified, then all the basis states representable by
         the device are considered and no marginalization takes place.
diff --git a/pennylane/measurements/sample.py b/pennylane/measurements/sample.py
index fb930acfe..53fc78137 100644
--- a/pennylane/measurements/sample.py
+++ b/pennylane/measurements/sample.py
@@ -109,13 +109,17 @@ def sample(op: Union[Observable, None] = None, wires=None):
             )
         wires = Wires(wires)
 
-    return _Sample(Sample, obs=op, wires=wires)
+    return _Sample(obs=op, wires=wires)
 
 
 # TODO: Make public when removing the ObservableReturnTypes enum
 class _Sample(SampleMeasurement):
     """Measurement process that returns the samples of a given observable."""
 
+    @property
+    def return_type(self):
+        return Sample
+
     def process_samples(
         self,
         samples: Sequence[complex],
diff --git a/pennylane/measurements/state.py b/pennylane/measurements/state.py
index 84092e419..298453a13 100644
--- a/pennylane/measurements/state.py
+++ b/pennylane/measurements/state.py
@@ -77,7 +77,7 @@ def state():
         -0.07471906623679961
     """
     # pylint: disable=protected-access
-    return _State(State)
+    return _State()
 
 
 def density_matrix(wires):
@@ -119,12 +119,16 @@ def density_matrix(wires):
     """
     # pylint: disable=protected-access
     wires = Wires(wires)
-    return _State(State, wires=wires)
+    return _State(wires=wires)
 
 
 class _State(StateMeasurement):
     """Measurement process that returns the quantum state."""
 
+    @property
+    def return_type(self):
+        return State
+
     # pylint: disable=redefined-outer-name
     def process_state(self, state: Sequence[complex], wire_order: Wires):
         if self.wires:
diff --git a/pennylane/measurements/var.py b/pennylane/measurements/var.py
index 9bc973a8a..44aa7dc4f 100644
--- a/pennylane/measurements/var.py
+++ b/pennylane/measurements/var.py
@@ -55,12 +55,16 @@ def var(op: Operator):
     """
     if not op.is_hermitian:
         warnings.warn(f"{op.name} might not be hermitian.")
-    return _Variance(Variance, obs=op)
+    return _Variance(obs=op)
 
 
 class _Variance(SampleMeasurement, StateMeasurement):
     """Measurement process that computes the variance of the supplied observable."""
 
+    @property
+    def return_type(self):
+        return Variance
+
     def process_samples(
         self,
         samples: Sequence[complex],
diff --git a/pennylane/measurements/vn_entropy.py b/pennylane/measurements/vn_entropy.py
index 87beb7773..2185d6053 100644
--- a/pennylane/measurements/vn_entropy.py
+++ b/pennylane/measurements/vn_entropy.py
@@ -22,7 +22,7 @@ import pennylane as qml
 from pennylane.operation import Operator
 from pennylane.wires import Wires
 
-from .measurements import ObservableReturnTypes, StateMeasurement, VnEntropy
+from .measurements import StateMeasurement, VnEntropy
 
 
 def vn_entropy(wires, log_base=None):
@@ -66,7 +66,7 @@ def vn_entropy(wires, log_base=None):
     .. seealso:: :func:`pennylane.qinfo.transforms.vn_entropy` and :func:`pennylane.math.vn_entropy`
     """
     wires = Wires(wires)
-    return _VnEntropy(VnEntropy, wires=wires, log_base=log_base)
+    return _VnEntropy(wires=wires, log_base=log_base)
 
 
 class _VnEntropy(StateMeasurement):
@@ -75,7 +75,6 @@ class _VnEntropy(StateMeasurement):
     # pylint: disable=too-many-arguments, unused-argument
     def __init__(
         self,
-        return_type: ObservableReturnTypes,
         obs: Operator = None,
         wires=None,
         eigvals=None,
@@ -83,7 +82,11 @@ class _VnEntropy(StateMeasurement):
         log_base=None,
     ):
         self.log_base = log_base
-        super().__init__(return_type=return_type, obs=obs, wires=wires, eigvals=eigvals, id=id)
+        super().__init__(obs=obs, wires=wires, eigvals=eigvals, id=id)
+
+    @property
+    def return_type(self):
+        return VnEntropy
 
     def process_state(self, state: Sequence[complex], wire_order: Wires):
         return qml.math.vn_entropy(
@@ -92,7 +95,6 @@ class _VnEntropy(StateMeasurement):
 
     def __copy__(self):
         return self.__class__(
-            self.return_type,
             obs=copy.copy(self.obs),
             wires=self._wires,
             eigvals=self._eigvals,
diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py
index 683465afc..ec7b3b317 100644
--- a/pennylane/tape/tape.py
+++ b/pennylane/tape/tape.py
@@ -177,9 +177,7 @@ def expand_tape(qscript, depth=1, stop_at=None, expand_measurements=False):
             qscript._ops.extend(rotations)
 
             for o, i in zip(diag_obs, qscript._obs_sharing_wires_id):
-                new_m = qml.measurements.MeasurementProcess(
-                    qscript.measurements[i].return_type, obs=o
-                )
+                new_m = qscript.measurements[i].__class__(obs=o)
                 qscript._measurements[i] = new_m
 
     for queue, new_queue in [
diff --git a/pennylane/transforms/qcut/qcut.py b/pennylane/transforms/qcut/qcut.py
index 823d133ba..5793cfff0 100644
--- a/pennylane/transforms/qcut/qcut.py
+++ b/pennylane/transforms/qcut/qcut.py
@@ -32,10 +32,10 @@ from networkx import MultiDiGraph, has_path, weakly_connected_components
 import pennylane as qml
 from pennylane import apply, expval
 from pennylane import numpy as np
-from pennylane.pauli import string_to_pauli_word
 from pennylane.measurements import Expectation, MeasurementProcess, Sample
 from pennylane.operation import Operation, Operator, Tensor
 from pennylane.ops.qubit.non_parametric_ops import WireCut
+from pennylane.pauli import string_to_pauli_word
 from pennylane.tape import QuantumTape
 from pennylane.wires import Wires
 
@@ -245,7 +245,7 @@ def tape_to_graph(tape: QuantumTape) -> MultiDiGraph:
                     "is not supported in circuit cutting"
                 )
             for o in obs.obs:
-                m_ = MeasurementProcess(m.return_type, obs=o)
+                m_ = m.__class__(obs=o)
 
                 _add_operator_node(graph, m_, order, wire_latest_node)
         elif m.return_type is Sample and obs is None:
diff --git a/pennylane/transforms/transpile.py b/pennylane/transforms/transpile.py
index 7daf80252..6766f4203 100644
--- a/pennylane/transforms/transpile.py
+++ b/pennylane/transforms/transpile.py
@@ -1,17 +1,18 @@
 """
 Contains the transpiler transform.
 """
-from typing import Union, List
+from typing import List, Union
+
 import networkx as nx
 
 import pennylane as qml
-from pennylane import apply, Hamiltonian
-from pennylane.ops.qubit import SWAP
+from pennylane import Hamiltonian, apply
 from pennylane.operation import Tensor
 from pennylane.ops import __all__ as all_ops
+from pennylane.ops.qubit import SWAP
+from pennylane.queuing import QueuingManager
 from pennylane.tape import QuantumTape
 from pennylane.transforms import qfunc_transform
-from pennylane.queuing import QueuingManager
 from pennylane.wires import Wires
 
 
@@ -180,7 +181,7 @@ def _adjust_mmt_indices(_m, _map_wires):
 
     # change wires of observable
     if _m.obs is None:
-        return type(_m)(return_type=_m.return_type, eigvals=qml.eigvals(_m), wires=_new_wires)
+        return type(_m)(eigvals=qml.eigvals(_m), wires=_new_wires)
 
     _new_obs = type(_m.obs)(wires=_new_wires, id=_m.obs.id)
-    return type(_m)(return_type=_m.return_type, obs=_new_obs)
+    return type(_m)(obs=_new_obs)


Test patch:
diff --git a/tests/measurements/test_counts.py b/tests/measurements/test_counts.py
index 947a31954..f95d3eb8a 100644
--- a/tests/measurements/test_counts.py
+++ b/tests/measurements/test_counts.py
@@ -12,12 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Unit tests for the counts module"""
+import copy
+
 import numpy as np
 import pytest
 
 import pennylane as qml
-from pennylane.measurements import AllCounts, Counts
+from pennylane.measurements import AllCounts, Counts, _Counts
 from pennylane.operation import Operator
+from pennylane.wires import Wires
 
 
 # TODO: Remove this when new CustomMP are the default
@@ -49,6 +52,32 @@ def custom_measurement_process(device, spy):
 class TestCounts:
     """Tests for the counts function"""
 
+    def test_counts_properties(self):
+        """Test that the properties are correct."""
+        meas1 = qml.counts(wires=0)
+        meas2 = qml.counts(op=qml.PauliX(0), all_outcomes=True)
+        assert meas1.return_type == Counts
+        assert meas2.return_type == AllCounts
+
+    def test_queue(self):
+        """Test that the right measurement class is queued."""
+        dev = qml.device("default.qubit", wires=2, shots=1000)
+
+        @qml.qnode(dev)
+        def circuit():
+            return qml.counts(wires=0)
+
+        circuit()
+
+        assert isinstance(circuit.tape[0], _Counts)
+
+    def test_copy(self):
+        """Test that the ``__copy__`` method also copies the ``all_outcomes`` information."""
+        meas = qml.counts(wires=0, all_outcomes=True)
+        meas_copy = copy.copy(meas)
+        assert meas_copy.wires == Wires(0)
+        assert meas_copy.all_outcomes == True
+
     def test_providing_observable_and_wires(self):
         """Test that a ValueError is raised if both an observable is provided and wires are
         specified"""
diff --git a/tests/measurements/test_measurements.py b/tests/measurements/test_measurements.py
index 3b689774a..ae82e3f32 100644
--- a/tests/measurements/test_measurements.py
+++ b/tests/measurements/test_measurements.py
@@ -17,19 +17,24 @@ import pytest
 
 import pennylane as qml
 from pennylane.measurements import (
-    AllCounts,
+    ClassicalShadow,
     Counts,
     Expectation,
     MeasurementProcess,
     MidMeasure,
-    MutualInfo,
     Probability,
     Sample,
-    Shadow,
-    ShadowExpval,
     State,
     Variance,
-    VnEntropy,
+    _Counts,
+    _Expectation,
+    _MutualInfo,
+    _Probability,
+    _Sample,
+    _ShadowExpval,
+    _State,
+    _Variance,
+    _VnEntropy,
     expval,
     sample,
     var,
@@ -38,6 +43,12 @@ from pennylane.operation import DecompositionUndefinedError
 from pennylane.queuing import AnnotatedQueue
 
 
+class NotValidMeasurement(MeasurementProcess):
+    @property
+    def return_type(self):
+        return "NotValidReturnType"
+
+
 @pytest.mark.parametrize(
     "return_type, value",
     [
@@ -75,7 +86,8 @@ def test_no_measure():
 def test_numeric_type_unrecognized_error():
     """Test that querying the numeric type of a measurement process with an
     unrecognized return type raises an error."""
-    mp = MeasurementProcess("NotValidReturnType")
+
+    mp = NotValidMeasurement()
     with pytest.raises(qml.QuantumFunctionError, match="Cannot deduce the numeric type"):
         mp.numeric_type()
 
@@ -83,7 +95,8 @@ def test_numeric_type_unrecognized_error():
 def test_shape_unrecognized_error():
     """Test that querying the shape of a measurement process with an
     unrecognized return type raises an error."""
-    mp = MeasurementProcess("NotValidReturnType")
+    dev = qml.device("default.qubit", wires=2)
+    mp = NotValidMeasurement()
     with pytest.raises(qml.QuantumFunctionError, match="Cannot deduce the shape"):
         mp.shape()
 
@@ -206,11 +219,15 @@ class TestStatisticsQueuing:
 class TestProperties:
     """Test for the properties"""
 
+    def test_return_type(self):
+        """Test MeasurementProcess ``return_type`` property is None."""
+        assert MeasurementProcess().return_type is None
+
     def test_wires_match_observable(self):
         """Test that the wires of the measurement process
         match an internal observable"""
         obs = qml.Hermitian(np.diag([1, 2, 3, 4]), wires=["a", "b"])
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = qml.expval(op=obs)
 
         assert np.all(m.wires == obs.wires)
 
@@ -218,7 +235,7 @@ class TestProperties:
         """Test that the eigenvalues of the measurement process
         match an internal observable"""
         obs = qml.Hermitian(np.diag([1, 2, 3, 4]), wires=[0, 1])
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = qml.expval(op=obs)
 
         assert np.all(m.eigvals() == np.array([1, 2, 3, 4]))
 
@@ -232,7 +249,7 @@ class TestProperties:
         obs = qml.Hermitian(np.diag([1, 2, 3, 4]), wires=[0, 1])
 
         with pytest.raises(ValueError, match="Cannot set the eigenvalues"):
-            MeasurementProcess(Expectation, obs=obs, eigvals=[0, 1])
+            _Expectation(obs=obs, eigvals=[0, 1])
 
     def test_error_obs_and_wires(self):
         """Test that providing both wires and an observable
@@ -240,22 +257,22 @@ class TestProperties:
         obs = qml.Hermitian(np.diag([1, 2, 3, 4]), wires=[0, 1])
 
         with pytest.raises(ValueError, match="Cannot set the wires"):
-            MeasurementProcess(Expectation, obs=obs, wires=qml.wires.Wires([0, 1]))
+            _Expectation(obs=obs, wires=qml.wires.Wires([0, 1]))
 
     def test_observable_with_no_eigvals(self):
         """An observable with no eigenvalues defined should cause
         the eigvals method to return a NotImplementedError"""
         obs = qml.NumberOperator(wires=0)
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = qml.expval(op=obs)
         assert m.eigvals() is None
 
     def test_repr(self):
         """Test the string representation of a MeasurementProcess."""
-        m = MeasurementProcess(Expectation, obs=qml.PauliZ(wires="a") @ qml.PauliZ(wires="b"))
+        m = qml.expval(op=qml.PauliZ(wires="a") @ qml.PauliZ(wires="b"))
         expected = "expval(PauliZ(wires=['a']) @ PauliZ(wires=['b']))"
         assert str(m) == expected
 
-        m = MeasurementProcess(Probability, obs=qml.PauliZ(wires="a"))
+        m = qml.probs(op=qml.PauliZ(wires="a"))
         expected = "probs(PauliZ(wires=['a']))"
         assert str(m) == expected
 
@@ -266,7 +283,7 @@ class TestExpansion:
     def test_expand_pauli(self):
         """Test the expansion of a Pauli observable"""
         obs = qml.PauliX(0) @ qml.PauliY(1)
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = qml.expval(op=obs)
         tape = m.expand()
 
         assert len(tape.operations) == 4
@@ -291,7 +308,7 @@ class TestExpansion:
         H = np.array([[1, 2], [2, 4]])
         obs = qml.Hermitian(H, wires=["a"])
 
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = qml.expval(op=obs)
         tape = m.expand()
 
         assert len(tape.operations) == 1
@@ -314,29 +331,28 @@ class TestExpansion:
         """Check that an exception is raised if the measurement to
         be expanded has no observable"""
         with pytest.raises(DecompositionUndefinedError):
-            MeasurementProcess(Probability, wires=qml.wires.Wires([0, 1])).expand()
+            _Probability(wires=qml.wires.Wires([0, 1])).expand()
 
     @pytest.mark.parametrize(
-        "return_type, obs",
+        "m",
         [
-            (Expectation, qml.PauliX(0) @ qml.PauliY(1)),
-            (Variance, qml.PauliX(0) @ qml.PauliY(1)),
-            (Probability, qml.PauliX(0) @ qml.PauliY(1)),
-            (Expectation, qml.PauliX(5)),
-            (Variance, qml.PauliZ(0) @ qml.Identity(3)),
-            (Probability, qml.PauliZ(0) @ qml.Identity(3)),
+            _Expectation(obs=qml.PauliX(0) @ qml.PauliY(1)),
+            _Variance(obs=qml.PauliX(0) @ qml.PauliY(1)),
+            _Probability(obs=qml.PauliX(0) @ qml.PauliY(1)),
+            _Expectation(obs=qml.PauliX(5)),
+            _Variance(obs=qml.PauliZ(0) @ qml.Identity(3)),
+            _Probability(obs=qml.PauliZ(0) @ qml.Identity(3)),
         ],
     )
-    def test_has_decomposition_true_pauli(self, return_type, obs):
+    def test_has_decomposition_true_pauli(self, m):
         """Test that measurements of Paulis report to have a decomposition."""
-        m = MeasurementProcess(return_type, obs=obs)
         assert m.has_decomposition is True
 
     def test_has_decomposition_true_hermitian(self):
         """Test that measurements of Hermitians report to have a decomposition."""
         H = np.array([[1, 2], [2, 4]])
         obs = qml.Hermitian(H, wires=["a"])
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = qml.expval(op=obs)
         assert m.has_decomposition is True
 
     def test_has_decomposition_false_hermitian_wo_diaggates(self):
@@ -349,52 +365,50 @@ class TestExpansion:
 
         H = np.array([[1, 2], [2, 4]])
         obs = HermitianNoDiagGates(H, wires=["a"])
-        m = MeasurementProcess(Expectation, obs=obs)
+        m = _Expectation(obs=obs)
         assert m.has_decomposition is False
 
     def test_has_decomposition_false_no_observable(self):
         """Check a MeasurementProcess without observable to report not having a decomposition"""
-        m = MeasurementProcess(Probability, wires=qml.wires.Wires([0, 1]))
+        m = _Probability(wires=qml.wires.Wires([0, 1]))
         assert m.has_decomposition is False
 
-        m = MeasurementProcess(Expectation, wires=qml.wires.Wires([0, 1]), eigvals=np.ones(4))
+        m = _Expectation(wires=qml.wires.Wires([0, 1]), eigvals=np.ones(4))
         assert m.has_decomposition is False
 
     @pytest.mark.parametrize(
-        "return_type, kwargs",
+        "m",
         [
-            (Sample, {}),
-            (Sample, {"wires": ["a", 1]}),
-            (AllCounts, {}),
-            (AllCounts, {"wires": ["a", 1]}),
-            (Counts, {}),
-            (Counts, {"wires": ["a", 1]}),
+            _Sample(),
+            _Sample(wires=["a", 1]),
+            _Counts(all_outcomes=True),
+            _Counts(wires=["a", 1], all_outcomes=True),
+            _Counts(),
+            _Counts(wires=["a", 1]),
         ],
     )
-    def test_samples_computational_basis_true(self, return_type, kwargs):
+    def test_samples_computational_basis_true(self, m):
         """Test that measurements of Paulis report to have a decomposition."""
-        m = MeasurementProcess(return_type, **kwargs)
         assert m.samples_computational_basis is True
 
     @pytest.mark.parametrize(
-        "return_type, arg",
+        "m",
         [
-            (Expectation, {"obs": qml.PauliX(2)}),
-            (Variance, {"obs": qml.PauliX("a")}),
-            (Probability, {"obs": qml.PauliX("b")}),
-            (Probability, {"wires": ["a", 1]}),
-            (Sample, {"obs": qml.PauliX("a")}),
-            (Counts, {"obs": qml.PauliX("a")}),
-            (State, {}),
-            (VnEntropy, {"wires": ["a", 1]}),
-            (MutualInfo, {"wires": [["a", 1], ["b", 2]]}),
-            (Shadow, {"wires": [["a", 1], ["b", 2]]}),
-            (ShadowExpval, {"obs": qml.PauliX("a")}),
+            _Expectation(obs=qml.PauliX(2)),
+            _Variance(obs=qml.PauliX("a")),
+            _Probability(obs=qml.PauliX("b")),
+            _Probability(wires=["a", 1]),
+            _Sample(obs=qml.PauliX("a")),
+            _Counts(obs=qml.PauliX("a")),
+            _State(),
+            _VnEntropy(wires=["a", 1]),
+            _MutualInfo(wires=[["a", 1], ["b", 2]]),
+            ClassicalShadow(wires=[["a", 1], ["b", 2]]),
+            _ShadowExpval(H=qml.PauliX("a")),
         ],
     )
-    def test_samples_computational_basis_false(self, return_type, arg):
+    def test_samples_computational_basis_false(self, m):
         """Test that measurements of Paulis report to have a decomposition."""
-        m = MeasurementProcess(return_type, **arg)
         assert m.samples_computational_basis is False
 
 
diff --git a/tests/measurements/test_mutual_info.py b/tests/measurements/test_mutual_info.py
index d73b13d3d..97133d012 100644
--- a/tests/measurements/test_mutual_info.py
+++ b/tests/measurements/test_mutual_info.py
@@ -12,12 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Unit tests for the mutual_info module"""
+import copy
+
 import numpy as np
 import pytest
 
 import pennylane as qml
 from pennylane.interfaces import INTERFACE_MAP
+from pennylane.measurements import MutualInfo
 from pennylane.measurements.mutual_info import _MutualInfo
+from pennylane.wires import Wires
 
 
 class TestMutualInfo:
@@ -63,3 +67,15 @@ class TestMutualInfo:
         circuit()
 
         assert isinstance(circuit.tape[0], _MutualInfo)
+
+    def test_properties(self):
+        """Test that the properties are correct."""
+        meas = qml.mutual_info(wires0=[0], wires1=[1])
+        assert meas.return_type == MutualInfo
+
+    def test_copy(self):
+        """Test that the ``__copy__`` method also copies the ``log_base`` information."""
+        meas = qml.mutual_info(wires0=[0], wires1=[1], log_base=2)
+        meas_copy = copy.copy(meas)
+        assert meas_copy.log_base == 2
+        assert meas_copy.wires == Wires([0, 1])
diff --git a/tests/measurements/test_probs.py b/tests/measurements/test_probs.py
index febba6611..c7f04c3bc 100644
--- a/tests/measurements/test_probs.py
+++ b/tests/measurements/test_probs.py
@@ -17,7 +17,12 @@ import pytest
 
 import pennylane as qml
 from pennylane import numpy as pnp
-from pennylane.measurements import MeasurementProcess, MeasurementShapeError, Probability
+from pennylane.measurements import (
+    MeasurementProcess,
+    MeasurementShapeError,
+    Probability,
+    _Probability,
+)
 from pennylane.queuing import AnnotatedQueue
 
 
@@ -66,14 +71,24 @@ def init_state():
     return _init_state
 
 
-# pylint: disable=redefined-outer-name
 class TestProbs:
     """Tests for the probs function"""
 
-    @pytest.mark.parametrize("wires", [[0], [2, 1], ["a", "c", 3]])
-    def test_numeric_type(self, wires):
+    def test_queue(self):
+        """Test that the right measurement class is queued."""
+        dev = qml.device("default.qubit", wires=2)
+
+        @qml.qnode(dev)
+        def circuit():
+            return qml.probs(wires=0)
+
+        circuit()
+
+        assert isinstance(circuit.tape[0], _Probability)
+
+    def test_numeric_type(self):
         """Test that the numeric type is correct."""
-        res = qml.probs(wires=wires)
+        res = qml.probs(wires=0)
         assert res.numeric_type is float
 
     @pytest.mark.parametrize("wires", [[0], [2, 1], ["a", "c", 3]])
diff --git a/tests/measurements/test_vn_entropy.py b/tests/measurements/test_vn_entropy.py
index c03e3a991..a0e461244 100644
--- a/tests/measurements/test_vn_entropy.py
+++ b/tests/measurements/test_vn_entropy.py
@@ -12,11 +12,15 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Unit tests for the vn_entropy module"""
+import copy
+
 import pytest
 
 import pennylane as qml
 from pennylane.interfaces import INTERFACE_MAP
+from pennylane.measurements import VnEntropy
 from pennylane.measurements.vn_entropy import _VnEntropy
+from pennylane.wires import Wires
 
 
 class TestVnEntropy:
@@ -57,3 +61,15 @@ class TestVnEntropy:
         circuit()
 
         assert isinstance(circuit.tape[0], _VnEntropy)
+
+    def test_copy(self):
+        """Test that the ``__copy__`` method also copies the ``log_base`` information."""
+        meas = qml.vn_entropy(wires=0, log_base=2)
+        meas_copy = copy.copy(meas)
+        assert meas_copy.log_base == 2
+        assert meas_copy.wires == Wires(0)
+
+    def test_properties(self):
+        """Test that the properties are correct."""
+        meas = qml.vn_entropy(wires=0)
+        assert meas.return_type == VnEntropy
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index f72e30d4e..90da53a6e 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -22,7 +22,7 @@ import pytest
 
 import pennylane as qml
 from pennylane import numpy as npp
-from pennylane.measurements import MeasurementProcess, ObservableReturnTypes
+from pennylane.measurements import _Expectation
 
 PARAMETRIZED_OPERATIONS_1P_1W = [
     qml.RX,
@@ -150,8 +150,8 @@ PARAMETRIZED_MEASUREMENTS = [
             [1.0, 1.0], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0) @ qml.PauliZ(1)]
         )
     ),
-    MeasurementProcess(ObservableReturnTypes.Expectation, eigvals=[1, -1]),
-    MeasurementProcess(ObservableReturnTypes.Expectation, eigvals=[1, 2]),
+    _Expectation(eigvals=[1, -1]),
+    _Expectation(eigvals=[1, 2]),
 ]
 PARAMETRIZED_MEASUREMENTS_COMBINATIONS = list(
     itertools.combinations(
diff --git a/tests/returntypes/test_new_qscript.py b/tests/returntypes/test_new_qscript.py
index 46c8b51c1..f9af5a312 100644
--- a/tests/returntypes/test_new_qscript.py
+++ b/tests/returntypes/test_new_qscript.py
@@ -27,13 +27,12 @@ from pennylane.measurements import (
     MeasurementShapeError,
     counts,
     expval,
+    probs,
     sample,
     var,
-    probs,
 )
 from pennylane.tape import QuantumScript
 
-
 measures = [
     (qml.expval(qml.PauliZ(0)), ()),
     (qml.var(qml.PauliZ(0)), ()),
diff --git a/tests/returntypes/test_new_return_types.py b/tests/returntypes/test_new_return_types.py
index 83ac9fff9..6dc45c516 100644
--- a/tests/returntypes/test_new_return_types.py
+++ b/tests/returntypes/test_new_return_types.py
@@ -18,6 +18,7 @@ import numpy as np
 import pytest
 
 import pennylane as qml
+from pennylane.measurements import MeasurementProcess
 
 wires = [2, 3, 4]
 
@@ -1210,11 +1211,14 @@ class TestQubitDeviceNewUnits:
     def test_unsupported_observable_return_type_raise_error(self):
         """Check that an error is raised if the return type of an observable is unsupported"""
 
+        class DummyMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return "SomeUnsupportedReturnType"
+
         with qml.tape.QuantumTape() as tape:
             qml.PauliX(wires=0)
-            qml.measurements.MeasurementProcess(
-                return_type="SomeUnsupportedReturnType", obs=qml.PauliZ(0)
-            )
+            DummyMeasurement(obs=qml.PauliZ(0))
 
         dev = qml.device("default.qubit", wires=3)
         with pytest.raises(
diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py
index 09a71323c..841aaa4b1 100644
--- a/tests/tape/test_tape.py
+++ b/tests/tape/test_tape.py
@@ -13,12 +13,10 @@
 # limitations under the License.
 """Unit tests for the QuantumTape"""
 import copy
-import warnings
 from collections import defaultdict
 
 import numpy as np
 import pytest
-from this import d
 
 import pennylane as qml
 from pennylane import CircuitGraph
@@ -27,11 +25,11 @@ from pennylane.measurements import (
     MeasurementShapeError,
     counts,
     expval,
+    probs,
     sample,
     var,
-    probs,
 )
-from pennylane.tape import QuantumTape, TapeError
+from pennylane.tape import QuantumTape
 
 
 def TestOperationMonkeypatching():
diff --git a/tests/test_qubit_device.py b/tests/test_qubit_device.py
index bd6f53c66..c67f4c1fd 100644
--- a/tests/test_qubit_device.py
+++ b/tests/test_qubit_device.py
@@ -280,11 +280,14 @@ class TestObservables:
     ):
         """Check that an error is raised if the return type of an observable is unsupported"""
 
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return "SomeUnsupportedReturnType"
+
         with qml.tape.QuantumTape() as tape:
             qml.PauliX(wires=0)
-            qml.measurements.MeasurementProcess(
-                return_type="SomeUnsupportedReturnType", obs=qml.PauliZ(0)
-            )
+            UnsupportedMeasurement(obs=qml.PauliZ(0))
 
         with monkeypatch.context() as m:
             m.setattr(QubitDevice, "apply", lambda self, x, **kwargs: None)
@@ -331,7 +334,12 @@ class TestExtractStatistics:
     def test_results_created(self, mock_qubit_device_extract_stats, monkeypatch, returntype):
         """Tests that the statistics method simply builds a results list without any side-effects"""
 
-        qscript = QuantumScript(measurements=[MeasurementProcess(returntype)])
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return returntype
+
+        qscript = QuantumScript(measurements=[UnsupportedMeasurement()])
 
         with monkeypatch.context() as m:
             dev = mock_qubit_device_extract_stats()
@@ -356,7 +364,12 @@ class TestExtractStatistics:
     def test_results_created_empty(self, mock_qubit_device_extract_stats, monkeypatch, returntype):
         """Tests that the statistics method returns an empty list if the return type is None"""
 
-        qscript = QuantumScript(measurements=[MeasurementProcess(returntype)])
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return returntype
+
+        qscript = QuantumScript(measurements=[UnsupportedMeasurement()])
 
         with monkeypatch.context() as m:
             dev = mock_qubit_device_extract_stats()
@@ -370,7 +383,12 @@ class TestExtractStatistics:
 
         assert returntype not in [Expectation, Variance, Sample, Probability, State, None]
 
-        qscript = QuantumScript(measurements=[MeasurementProcess(returntype)])
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return returntype
+
+        qscript = QuantumScript(measurements=[UnsupportedMeasurement()])
 
         with pytest.raises(qml.QuantumFunctionError, match="Unsupported return type"):
             dev = mock_qubit_device_extract_stats()
diff --git a/tests/test_qutrit_device.py b/tests/test_qutrit_device.py
index 7ba892d46..49714b525 100644
--- a/tests/test_qutrit_device.py
+++ b/tests/test_qutrit_device.py
@@ -31,7 +31,6 @@ from pennylane.measurements import (
     Sample,
     State,
     Variance,
-    state,
 )
 from pennylane.tape import QuantumScript
 from pennylane.wires import Wires
@@ -258,13 +257,17 @@ class TestObservables:
 
     def test_unsupported_observable_return_type_raise_error(self, mock_qutrit_device, monkeypatch):
         """Check that an error is raised if the return type of an observable is unsupported"""
+
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return "SomeUnsupportedReturnType"
+
         U = unitary_group.rvs(3, random_state=10)
 
         with qml.tape.QuantumTape() as tape:
             qml.QutritUnitary(U, wires=0)
-            qml.measurements.MeasurementProcess(
-                return_type="SomeUnsupportedReturnType", obs=qml.Identity(0)
-            )
+            UnsupportedMeasurement(obs=qml.Identity(0))
 
         with monkeypatch.context() as m:
             m.setattr(QutritDevice, "apply", lambda self, x, **kwargs: None)
@@ -298,7 +301,12 @@ class TestExtractStatistics:
     def test_results_created(self, mock_qutrit_device_extract_stats, monkeypatch, returntype):
         """Tests that the statistics method simply builds a results list without any side-effects"""
 
-        qscript = QuantumScript(measurements=[MeasurementProcess(returntype)])
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return returntype
+
+        qscript = QuantumScript(measurements=[UnsupportedMeasurement()])
 
         with monkeypatch.context() as m:
             dev = mock_qutrit_device_extract_stats()
@@ -323,7 +331,12 @@ class TestExtractStatistics:
     def test_results_created_empty(self, mock_qutrit_device_extract_stats, monkeypatch, returntype):
         """Tests that the statistics method returns an empty list if the return type is None"""
 
-        qscript = QuantumScript(measurements=[MeasurementProcess(returntype)])
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return returntype
+
+        qscript = QuantumScript(measurements=[UnsupportedMeasurement()])
 
         with monkeypatch.context() as m:
             dev = mock_qutrit_device_extract_stats()
@@ -339,7 +352,12 @@ class TestExtractStatistics:
 
         assert returntype not in [Expectation, Variance, Sample, Probability, State, Counts, None]
 
-        qscript = QuantumScript(measurements=[MeasurementProcess(returntype)])
+        class UnsupportedMeasurement(MeasurementProcess):
+            @property
+            def return_type(self):
+                return returntype
+
+        qscript = QuantumScript(measurements=[UnsupportedMeasurement()])
 
         dev = mock_qutrit_device_extract_stats()
         with pytest.raises(qml.QuantumFunctionError, match="Unsupported return type"):