class_docstr: `FunctionMaker` is the class to `create` `Function` instances.
  
  This class has the fgraph, the rewriter, and the linker. When
  copying a `Function`, there is no need to duplicate the
  `FunctionMaker` instance. Deepcopy still copies both, which can
  variable in re-compilation.
  
  Parameters
  ----------
  inputs : list of SymbolicInput instances
  outputs : list of SymbolicOutput instances
      Outputs may also be a single Variable (not a list), in which case the
      functions produced by FunctionMaker will return their output value
      directly.
  mode : Mode instance
      Telling FunctionMaker how to rewrite and link. None means to use the
      `config.mode`.
  accept_inplace : bool
      True iff it is acceptable to have inplace operations in the graph from
      the inputs to the outputs.
  on_unused_input : {'raise', 'warn', 'ignore', None}
      What to do if a variable in the 'inputs' list is not used in the graph.
      Possible values are:
      - 'raise': raise an error
      - 'warn': log a warning
      - 'ignore': do not do anything
      - None: Use the value in the PyTensor flags on_unused_input.
  name : str
      An optional name for this function. If used, the profile mode will
      print the time spent in this function.
code: def __init__(
      self,
      inputs,
      outputs,
      mode=None,
      accept_inplace=False,
      function_builder=Function,
      profile=None,
      on_unused_input=None,
      fgraph=None,
      output_keys=None,
      name=None,
      no_fgraph_prep=False,
  ):
      # Save the provided mode, not the instantiated mode.
      # The instantiated mode don't pickle and if we unpickle an PyTensor
      # function and it get re-compiled, we want the current rewriter to be
      # used, not the rewriter when it was saved.
      self.mode = mode
      mode = pytensor.compile.mode.get_mode(mode)
  
      # Assert old way of working isn't used
      if getattr(mode, "profile", None):
          raise TypeError(
              "profile passed via 'mode'. This isn't supported anymore"
          )
      self.profile = profile
      if profile and config.cxx:
          # This is very important:
          # 1) We preload the cache here to not have its timing
          #    included with the rewrites.
          # 2) Do not refresh the cache here by default. It cause
          #    too much execution time during testing as we compile
          #    much more functions then the number of compile c
          #    module.
          start_get_cache = time.perf_counter()
          pytensor.link.c.basic.get_module_cache().refresh()
          get_cache_time = time.perf_counter() - start_get_cache
          self.profile.linker_time += get_cache_time
          self.profile.preload_cache_time += get_cache_time
  
      # Handle the case where inputs and/or outputs is a single
      # Variable (not in a list)
      unpack_single = False
      return_none = False
      if outputs is None:
          return_none = True
          outputs = []
      if not isinstance(outputs, (list, tuple)):
          unpack_single = True
          outputs = [outputs]
      if not isinstance(inputs, (list, tuple)):
          inputs = [inputs]
  
      # Wrap them in In or Out instances if needed.
      inputs = [self.wrap_in(i) for i in inputs]
      outputs = [self.wrap_out(o) for o in outputs]
  
      # Check if some input variables are unused
      self.check_unused_inputs(inputs, outputs, on_unused_input)
  
      indices = [[input, None, [input]] for input in inputs]
  
      fgraph, found_updates = std_fgraph(
          inputs, outputs, accept_inplace, fgraph=fgraph
      )
  
      if fgraph.profile is None:
          fgraph.profile = profile
  
      self.fgraph = fgraph
  
      if not no_fgraph_prep:
          self.prepare_fgraph(
              inputs, outputs, found_updates, fgraph, mode, profile
          )
  
      assert len(fgraph.outputs) == len(outputs + found_updates)
  
      # The 'no_borrow' outputs are the ones for which that we can't
      # return the internal storage pointer.
      no_borrow = [
          output
          for output, spec in zip(
              fgraph.outputs, outputs + found_updates
          )
          if not spec.borrow
      ]
  
      linker = copy.copy(mode.linker)
  
      if no_borrow:
          self.linker = linker.accept(
              fgraph,
              no_recycling=infer_reuse_pattern(fgraph, no_borrow),
              profile=profile,
          )
      else:
          self.linker = linker.accept(fgraph, profile=profile)
  
      if hasattr(linker, "accept_var_updates"):
          # TODO: This is a hack that makes `VMLinker` aware of updates;
          # clean this up.
          self.linker.accept_var_updates(
              fgraph_updated_vars(fgraph, inputs)
          )
  
      fgraph.name = name
      self.indices = indices
      self.inputs = inputs
  
      # TODO: Get rid of all this `expanded_inputs` nonsense
      self.expanded_inputs = inputs
      self.outputs = outputs
      self.unpack_single = unpack_single
      self.return_none = return_none
      self.accept_inplace = accept_inplace
      self.function_builder = function_builder
      self.on_unused_input = (
          on_unused_input  # Used for the pickling/copy
      )
      self.output_keys = output_keys
      self.name = name
  
      self.required = [(i.value is None) for i in self.inputs]
      self.refeed = [
          (
              i.value is not None
              and not isinstance(i.value, Container)
              and i.update is None
          )
          for i in self.inputs
      ]
  
imports: import copy
  import copyreg
  import logging
  import time
  import warnings
  from itertools import chain
  from typing import TYPE_CHECKING, Optional
  import numpy
  import pytensor
  import pytensor.compile.profiling
  from pytensor.compile.io import In, SymbolicInput, SymbolicOutput
  from pytensor.compile.ops import deep_copy_op, view_op
  from pytensor.configdefaults import config
  from pytensor.graph.basic import Constant, Variable, ancestors, clone_get_equiv, graph_inputs
  from pytensor.graph.destroyhandler import DestroyHandler
  from pytensor.graph.features import AlreadyThere, Feature, PreserveVariableAttributes
  from pytensor.graph.fg import FunctionGraph
  from pytensor.graph.op import HasInnerGraph
  from pytensor.graph.utils import InconsistencyError, get_variable_trace_string
  from pytensor.link.basic import Container
  from pytensor.link.utils import raise_with_op
label_desc: Use image node_id 5 to create a new FunctionMaker object  with example: obj = FunctionMaker(inputs, outputs, mode, accept_inplace, function_builder, profile, on_unused_input, fgraph, output_keys, name, no_fgraph_prep)