text: import sys
  import collections
  
  
  RESERVED_NAMES = ("get", "items")
  
  def tuple_itemgetter(i):
      def _tuple_itemgetter(obj):
          return tuple.__getitem__(obj, i)
      return _tuple_itemgetter
  
  def namedarraytuple(typename, field_names, return_namedtuple_cls=False,
          classname_suffix=False):
      """
      Returns a new subclass of a namedtuple which exposes indexing / slicing
      reads and writes applied to all contained objects, which must share
      indexing (__getitem__) behavior (e.g. numpy arrays or torch tensors).
      (Code follows pattern of collections.namedtuple.)
      >>> PointsCls = namedarraytuple('Points', ['x', 'y'])
      >>> p = PointsCls(np.array([0, 1]), y=np.array([10, 11]))
      >>> p
      Points(x=array([0, 1]), y=array([10, 11]))
      >>> p.x                         # fields accessible by name
      array([0, 1])
      >>> p[0]                        # get location across all fields
      Points(x=0, y=10)               # (location can be index or slice)
      >>> p.get(0)                    # regular tuple-indexing into field
      array([0, 1])
      >>> x, y = p                    # unpack like a regular tuple
      >>> x
      array([0, 1])
      >>> p[1] = 2                    # assign value to location of all fields
      >>> p
      Points(x=array([0, 2]), y=array([10, 2]))
      >>> p[1] = PointsCls(3, 30)     # assign to location field-by-field
      >>> p
      Points(x=array([0, 3]), y=array([10, 30]))
      >>> 'x' in p                    # check field name instead of object
      True
      """
      nt_typename = typename
      if classname_suffix:
          nt_typename += "_nt"  # Helpful to identify which style of tuple.
          typename += "_nat"
  
      try:  # For pickling, get location where this function was called.
          # NOTE: (pickling might not work for nested class definition.)
          module = sys._getframe(1).f_globals.get('__name__', '__main__')
      except (AttributeError, ValueError):
          module = None
      NtCls = collections.namedtuple(nt_typename, field_names, module=module)
  
      def __getitem__(self, loc):
          try:
              return type(self)(*({k: v[loc] for k, v in s.items()} 
                                  if isinstance(s, dict) else s[loc] for s in self))
          except IndexError as e:
              for j, s in enumerate(self):
                  if s is None:
                      continue
                  try:
                      _ = s[loc]
                  except IndexError:
                      raise Exception(f"Occured in {self.__class__} at field "
                          f"'{self._fields[j]}'.") from e
  
      __getitem__.__doc__ = (f"Return a new {typename} instance containing "
          "the selected index or slice from each field.")
  
      def __setitem__(self, loc, value):
          """
          If input value is the same named[array]tuple type, iterate through its
          fields and assign values into selected index or slice of corresponding
          field.  Else, assign whole of value to selected index or slice of
          all fields.  Ignore fields that are both None.
          """
          if not (isinstance(value, tuple) and  # Check for matching structure.
                  getattr(value, "_fields", None) == self._fields):
              # Repeat value for each but respect any None.
              value = tuple({k: v[loc] for k, v in s.items()} 
                            if isinstance(s, dict) else value for s in self)
          try:
              for j, (s, v) in enumerate(zip(self, value)):
                  if isinstance(s, dict):
                      for k in s.keys():
                          s[k][loc] = v[k]
                  else:
                      s[loc] = v
          except (ValueError, IndexError, TypeError) as e:
              raise Exception(f"Occured in {self.__class__} at field "
                  f"'{self._fields[j]}'.") from e
  
      def __contains__(self, key):
          "Checks presence of field name (unlike tuple; like dict)."
          return key in self._fields
  
      def get(self, index):
          "Retrieve value as if indexing into regular tuple."
          return tuple.__getitem__(self, index)
  
      def items(self):
          "Iterate ordered (field_name, value) pairs (like OrderedDict)."
          for k, v in zip(self._fields, self):
              yield k, v
  
      for method in (__getitem__, __setitem__, get, items):
          method.__qualname__ = f'{typename}.{method.__name__}'
  
      arg_list = repr(NtCls._fields).replace("'", "")[1:-1]
      class_namespace = {
          '__doc__': f'{typename}({arg_list})',
          '__slots__': (),
          '__getitem__': __getitem__,
          '__setitem__': __setitem__,
          '__contains__': __contains__,
          'get': get,
          'items': items,
      }
  
      for index, name in enumerate(NtCls._fields):
          if name in RESERVED_NAMES:
              raise ValueError(f"Disallowed field name: {name}.")
          itemgetter_object = tuple_itemgetter(index)
          doc = f'Alias for field number {index}'
          class_namespace[name] = property(itemgetter_object, doc=doc)
  
      result = type(typename, (NtCls,), class_namespace)
      result.__module__ = NtCls.__module__
  
      if return_namedtuple_cls:
          return result, NtCls
      return result
  
  # for multi-processing efficiency, we do not return info at every step
  EnvOutput = namedarraytuple('EnvOutput', 'obs reward discount reset')
  # Output format of gym
  GymOutput = namedarraytuple('GymOutput', 'obs reward discount')
  # LSTM states
  LSTMState = collections.namedtuple('LSTMState', ['h', 'c'])
  # GRU states
  GRUState = collections.namedtuple('GRUState', ['h'])
  