class_docstr: non-mutable structure that contains information about one side of expression (e.g. 'b c (h w)')
  and keeps some information important for downstream
code: def check_axis_name_return_reason(
      name: str, allow_underscore: bool = False
  ) -> Tuple[bool, str]:
      if not str.isidentifier(name):
          return False, "not a valid python identifier"
      elif name[0] == "_" or name[-1] == "_":
          if name == "_" and allow_underscore:
              return True, ""
          return (
              False,
              "axis name should should not start or end with underscore",
          )
      else:
          if keyword.iskeyword(name):
              warnings.warn(
                  "It is discouraged to use axes names that are keywords: {}".format(
                      name
                  ),
                  RuntimeWarning,
              )
          if name in ["axis"]:
              warnings.warn(
                  "It is discouraged to use 'axis' as an axis name "
                  "and will raise an error in future",
                  FutureWarning,
              )
          return True, ""
  
file_path: einops/einops/parsing.py
imports: from einops import EinopsError
  import keyword
  import warnings
  from typing import List, Optional, Set, Tuple, Union
label_desc: Use image node_id 4 for calling the ParsedExpression obj's underlying member method code with example usage: obj.check_axis_name_return_reason(name, allow_underscore) and returns: False,  str, False,  str, True,  str, True,  str