Repository: PennyLaneAI/pennylane

Issue:
[BUG] qml.apply duplicates new ops
### Expected behavior

`qml.apply` adds a single instance of a new operation to a circuit

### Actual behavior

`qml.apply` adds two instances of a new operation to a circuit

### Additional information

_No response_

### Source code

```shell
import pennylane as qml


def my_quantum_function():
    qml.RZ(.1, wires=0)
    return qml.expval(qml.PauliZ(0))


op = qml.RZ(.1, wires=0)
def my_quantum_function_with_apply_pre_instantiated():
    qml.apply(op)
    return qml.expval(qml.PauliZ(0))


def my_quantum_function_with_apply_new_op():
    qml.apply(qml.RZ(.1, wires=0))
    return qml.expval(qml.PauliZ(0))


print("no apply:      ", qml.draw(my_quantum_function)())
print("apply existing:", qml.draw(my_quantum_function_with_apply_pre_instantiated)())
print("apply new:     ", qml.draw(my_quantum_function_with_apply_new_op)())
```


### Tracebacks

```shell
no apply:       0: ──RZ(0.10)─┤  <Z>
apply existing: 0: ──RZ(0.10)─┤  <Z>
apply new:      0: ──RZ(0.10)──RZ(0.10)─┤  <Z>
```


### System information

```shell
Name: PennyLane
Version: 0.32.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /Users/berdy/Desktop/braket/pennylane
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           macOS-10.16-x86_64-i386-64bit
Python version:          3.10.11
Numpy version:           1.23.5
Scipy version:           1.10.0
Installed devices:
- default.gaussian (PennyLane-0.32.0.dev0)
- default.mixed (PennyLane-0.32.0.dev0)
- default.qubit (PennyLane-0.32.0.dev0)
- default.qubit.autograd (PennyLane-0.32.0.dev0)
- default.qubit.jax (PennyLane-0.32.0.dev0)
- default.qubit.tf (PennyLane-0.32.0.dev0)
- default.qubit.torch (PennyLane-0.32.0.dev0)
- default.qutrit (PennyLane-0.32.0.dev0)
- null.qubit (PennyLane-0.32.0.dev0)
- lightning.qubit (PennyLane-Lightning-0.31.0)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.


Discussion:
Hi @ajberdy, thanks for reporting this! Although it may seem unintuitive, this is actually the intended behaviour of `qml.apply`. You're tapping into queuing, one of the more complex internal details of PennyLane, and it can certainly get confusing. Your 3 examples actually very nicely outline the various ways an operation can be "queued". `qml.apply` does not need to be used in the third example, because it's the same as the first one but with an additional `qml.apply`.

The third one can be re-written as follows to shed a bit more light onto what's happening:
```python
def my_quantum_function_with_apply_new_op():
    op = qml.RZ(.1, wires=0)  # the creation of the RZ also queues it automatically
    qml.apply(op)  # this creates a copy, and queues that as well
    return qml.expval(qml.PauliZ(0))
```

The copy-and-queue feature is really useful for the second example you gave, when the operator was defined outside of the queuing context (aka the QNode, in your case). Though the difference between these examples may feel trivial, `qml.apply` becomes a lot more useful when you're queuing complicated operators that involve more advanced construction. Or perhaps even something like this:

```python
def big_op_that_takes_time(wires):
    return SlowOp(wires)

@qml.qnode(dev)
def circuit():
    slow_op = big_op_that_takes_time([0, 1, 2])
    some_ansatz([0, 1, 2])
    qml.apply(slow_op)
```

^ imagine that `big_op_that_takes_time` is an expensive function that eventually returns some operator. This way, you could queue it before _and_ after some ansatz, but only calling that slow function once!

I'll add an additional disclaimer to the docs that clarifies what happens in the third scenario you provided.
Thanks for the detailed explanation, makes sense! Feel free to close the issue or leave it open pending the docs update!

Resolving patch:
diff --git a/pennylane/queuing.py b/pennylane/queuing.py
--- a/pennylane/queuing.py
+++ b/pennylane/queuing.py
@@ -459,6 +459,22 @@ def circuit(x):
     >>> print(qml.draw(circuit)(0.6))
     0: ──RX(0.4)──RY(0.6)──RX(0.4)──┤ ⟨Z⟩
 
+    .. warning::
+
+        If you use ``apply`` on an operator that has already been queued, it will
+        be queued for a second time. For example:
+
+        .. code-block:: python
+
+            @qml.qnode(dev)
+            def circuit():
+                op = qml.Hadamard(0)
+                qml.apply(op)
+                return qml.expval(qml.PauliZ(0))
+
+        >>> print(qml.draw(circuit)())
+        0: ──H──H─┤  <Z>
+
     .. details::
         :title: Usage Details
 


Test patch: