I am currently trying to replace a whole class Instruction from an external module with my own implementation NamedRoutine. I want the external module to use my re-definition instead of the original class Instruction.
In order to have the best compatibility possible, and because I don't want to re-type the Instruction class, the redefined class inherits from Instruction. The NamedRoutine implementation is:
from qiskit.circuit.instruction import Instruction
from ._interfaces import interfaces
class NamedRoutine(Instruction, interfaces.NamedRoutine):
    def __init__(self, name: str, num_qubits: int, num_clbits: int, params):
        super().__init__(
            name=name, num_qubits=num_qubits, num_clbits=num_clbits, params=params
        )
        self.name = name
    @staticmethod
    def from_Routine(rout: Instruction) -> "NamedRoutine":
        named_routine = NamedRoutine(
            rout.name, rout.num_qubits, rout.num_clbits, rout.params
        )
        named_routine.definition = rout.definition
        return named_routine
    @staticmethod
    def from_Routine_and_name(rout: Instruction, name: str) -> "NamedRoutine":
        named_routine = NamedRoutine(
            name, rout.num_qubits, rout.num_clbits, rout.params
        )
        named_routine.definition = rout.definition
        return named_routine
    def inverse(self) -> "NamedRoutine":
        return NamedRoutine.from_Routine_and_name(
            super().inverse(), name="D-" + self.name
        )
As you can see, there is no compatibility issue between the 2 classes. The NamedRoutine implementation just ensure that a proper name is set, adds 2 static methods and overloads one method of Instruction to change the instance name attribute accordingly.
In order to replace completely the definition of qiskit.circuit.instruction.Instruction by my NamedRoutine implementation, I tried to call the following function before any qiskit-related code is imported:
def _wrap_instruction_class():
    import qiskit.circuit.instruction
    from .NamedRoutine import NamedRoutine
    setattr(qiskit.circuit.instruction, "Instruction", NamedRoutine)
    setattr(qiskit.circuit, "Instruction", NamedRoutine)
Note: Instruction is imported in the __init__.py file of qiskit.circuit.
This method nearly works as I am successfully replacing the definitions of Instruction by my own implementation.
The problem I am still facing is the following: when importing qiskit.circuit.instruction, the __init__.py files of the qiskit and qiskit.circuit modules are executed, and load a ton of methods/classes with the original definition of the Instruction method (as it is still not replaced).
The number of methods/functions/classes using this Instruction class being quite huge, I don't want to track them one by one. Moreover, this solution would require me to check after each update of the qiskit library if one more routine/function/class need to be "injected".
I also searched for a solution "unimporting" everything from the qiskit library except the Instruction class, and then re-importing all the relevant parts once the Instruction class has been modified, but it appears that unimporting is not possible in Python.
One possible solution would be to recursively import all the packages/modules of the qiskit library and replace the symbol Instruction with my definition if it is defined in the module, but this would require to import all the modules from qiskit. My software is not really constrained by runtime, but this takes approximately 4 seconds on my laptop with a recent SSD and a rather clean (low number of packages) virtual environment, and it may take a lot more depending on the packages installed on the machine.
Do any of you have another solution, possibly more efficient than iterating over all the modules to find the modules in qiskit where the Instruction symbol is defined and replace it?
