docs: def get_hyperedge_attribute(self, hyperedge_id, attribute_name):
          # Note: Code unchanged from DirectedHypergraph
          """Given a hyperedge ID and the name of an attribute, get a copy
          of that hyperedge's attribute.
  
          :param hyperedge_id: ID of the hyperedge to retrieve the attribute of.
          :param attribute_name: name of the attribute to retrieve.
          :returns: attribute value of the attribute_name key for the
                  specified hyperedge.
          :raises: ValueError -- No such hyperedge exists.
          :raises: ValueError -- No such attribute exists.
  
          Examples:
          ::
  
              >>> H = UndirectedHypergraph()
              >>> hyperedge_list = (["A", "B", "C"],
                                    ("A", "D"),
                                    set(["B", "D"]))
              >>> hyperedge_ids = H.add_hyperedges(hyperedge_list)
              >>> attribute = H.get_hyperedge_attribute(hyperedge_ids[0])
  
          """
          if not self.has_hyperedge_id(hyperedge_id):
              raise ValueError("No such hyperedge exists.")
          elif attribute_name not in self._hyperedge_attributes[hyperedge_id]:
              raise ValueError("No such attribute exists.")
          else:
              return copy.\
                  copy(self._hyperedge_attributes[hyperedge_id][attribute_name])