Repository: Qiskit/qiskit-terra (python)

Issue:
Add a new QuantumCircuit constructor QuantumCircuit.from_instructions
### What should we add?

Creating a QuantumCircuit and appending operations into it is a very common pattern. It would be very convenient to be able to create a QuantumCircuit by directly passing an iterator of operations, something like:
```python
@staticmethod
def from_ops(ops: Iterator[tuple[Instruction, Sequence[Qubit], Sequence[Clbit]]]) -> QuantumCircuit:
    circuit = QuantumCircuit()
    for op in ops:
        circuit.append(*ops)
    return circuit
```
Currently this is not possible as written but it would be if my proposal https://github.com/Qiskit/qiskit-terra/issues/8707 were implemented.

Pull request description:
add QuantumCircuit.from_instructions
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary
Fixes #8709 


### Details and comments




Patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
index 78b2273ab..8d959a244 100644
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -14,6 +14,7 @@
 
 """Quantum circuit object."""
 
+from __future__ import annotations
 import copy
 import itertools
 import functools
@@ -285,6 +286,45 @@ class QuantumCircuit:
             raise TypeError("Only a dictionary or None is accepted for circuit metadata")
         self._metadata = metadata
 
+    @staticmethod
+    def from_instructions(
+        instructions: Iterable[
+            CircuitInstruction
+            | tuple[qiskit.circuit.Instruction]
+            | tuple[qiskit.circuit.Instruction, Iterable[Qubit]]
+            | tuple[qiskit.circuit.Instruction, Iterable[Qubit], Iterable[Clbit]]
+        ],
+        *,
+        name: Optional[str] = None,
+        global_phase: ParameterValueType = 0,
+        metadata: Optional[dict] = None,
+    ) -> "QuantumCircuit":
+        """Construct a circuit from an iterable of CircuitInstructions.
+
+        Args:
+            instructions: The instructions to add to the circuit.
+            name: The name of the circuit.
+            global_phase: The global phase of the circuit in radians.
+            metadata: Arbitrary key value metadata to associate with the circuit.
+
+        Returns:
+            The quantum circuit.
+        """
+        circuit = QuantumCircuit(name=name, global_phase=global_phase, metadata=metadata)
+        added_qubits = set()
+        added_clbits = set()
+        for instruction in instructions:
+            if not isinstance(instruction, CircuitInstruction):
+                instruction = CircuitInstruction(*instruction)
+            qubits = [qubit for qubit in instruction.qubits if qubit not in added_qubits]
+            clbits = [clbit for clbit in instruction.clbits if clbit not in added_clbits]
+            circuit.add_bits(qubits)
+            circuit.add_bits(clbits)
+            added_qubits.update(qubits)
+            added_clbits.update(clbits)
+            circuit._append(instruction)
+        return circuit
+
     @property
     def data(self) -> QuantumCircuitData:
         """Return the circuit data (instructions and context).
diff --git a/releasenotes/notes/circuit-from-instructions-832b43bfd2bfd921.yaml b/releasenotes/notes/circuit-from-instructions-832b43bfd2bfd921.yaml
new file mode 100644
index 000000000..36ae1ca69
--- /dev/null
+++ b/releasenotes/notes/circuit-from-instructions-832b43bfd2bfd921.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Added the method :meth:`.QuantumCircuit.from_instructions`
+    for constructing a quantum circuit from an iterable of instructions.


Test patch:
diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py
index a0e87a980..f028984c9 100644
--- a/test/python/circuit/test_circuit_operations.py
+++ b/test/python/circuit/test_circuit_operations.py
@@ -13,21 +13,23 @@
 
 """Test Qiskit's QuantumCircuit class."""
 
-from ddt import ddt, data
 import numpy as np
-from qiskit import BasicAer
-from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
-from qiskit import execute
-from qiskit.circuit import Gate, Instruction, Parameter, Measure
+from ddt import data, ddt
+
+from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister, execute
+from qiskit.circuit import Gate, Instruction, Measure, Parameter
 from qiskit.circuit.bit import Bit
 from qiskit.circuit.classicalregister import Clbit
 from qiskit.circuit.exceptions import CircuitError
+from qiskit.circuit.controlflow import IfElseOp
+from qiskit.circuit.library import CXGate, HGate
+from qiskit.circuit.library.standard_gates import SGate
 from qiskit.circuit.quantumcircuit import BitLocations
+from qiskit.circuit.quantumcircuitdata import CircuitInstruction
 from qiskit.circuit.quantumregister import AncillaQubit, AncillaRegister, Qubit
-from qiskit.test import QiskitTestCase
-from qiskit.circuit.library.standard_gates import SGate
+from qiskit.pulse import DriveChannel, Gaussian, Play, Schedule
 from qiskit.quantum_info import Operator
-from qiskit.pulse import Schedule, Play, Gaussian, DriveChannel
+from qiskit.test import QiskitTestCase
 
 
 @ddt
@@ -1262,6 +1264,71 @@ class TestCircuitOperations(QiskitTestCase):
 
         self.assertEqual(test, expected)
 
+    def test_from_instructions(self):
+        """Test from_instructions method."""
+
+        qreg = QuantumRegister(4)
+        creg = ClassicalRegister(3)
+
+        a, b, c, d = qreg
+        x, y, z = creg
+
+        circuit_1 = QuantumCircuit(2)
+        circuit_1.x(0)
+        circuit_2 = QuantumCircuit(2)
+        circuit_2.y(0)
+
+        def instructions():
+            yield CircuitInstruction(HGate(), [a], [])
+            yield CircuitInstruction(CXGate(), [a, b], [])
+            yield CircuitInstruction(Measure(), [a], [x])
+            yield CircuitInstruction(Measure(), [b], [y])
+            yield CircuitInstruction(IfElseOp((z, 1), circuit_1, circuit_2), [c, d], [z])
+
+        def instruction_tuples():
+            yield HGate(), [a], []
+            yield CXGate(), [a, b], []
+            yield CircuitInstruction(Measure(), [a], [x])
+            yield Measure(), [b], [y]
+            yield IfElseOp((z, 1), circuit_1, circuit_2), [c, d], [z]
+
+        def instruction_tuples_partial():
+            yield HGate(), [a]
+            yield CXGate(), [a, b], []
+            yield CircuitInstruction(Measure(), [a], [x])
+            yield Measure(), [b], [y]
+            yield IfElseOp((z, 1), circuit_1, circuit_2), [c, d], [z]
+
+        circuit = QuantumCircuit.from_instructions(instructions())
+        circuit_tuples = QuantumCircuit.from_instructions(instruction_tuples())
+        circuit_tuples_partial = QuantumCircuit.from_instructions(instruction_tuples_partial())
+
+        expected = QuantumCircuit([a, b, c, d], [x, y, z])
+        for instruction in instructions():
+            expected.append(*instruction)
+
+        self.assertEqual(circuit, expected)
+        self.assertEqual(circuit_tuples, expected)
+        self.assertEqual(circuit_tuples_partial, expected)
+
+    def test_from_instructions_metadata(self):
+        """Test from_instructions method passes metadata."""
+        qreg = QuantumRegister(2)
+        a, b = qreg
+
+        def instructions():
+            yield CircuitInstruction(HGate(), [a], [])
+            yield CircuitInstruction(CXGate(), [a, b], [])
+
+        circuit = QuantumCircuit.from_instructions(instructions(), name="test", global_phase=0.1)
+
+        expected = QuantumCircuit([a, b], global_phase=0.1)
+        for instruction in instructions():
+            expected.append(*instruction)
+
+        self.assertEqual(circuit, expected)
+        self.assertEqual(circuit.name, "test")
+
 
 class TestCircuitPrivateOperations(QiskitTestCase):
     """Direct tests of some of the private methods of QuantumCircuit.  These do not represent