docs: def _get_lines_from_file(self, filename, lineno, context_lines, loader=None, module_name=None):
          """
          Returns context_lines before and after lineno from file.
          Returns (pre_context_lineno, pre_context, context_line, post_context).
          """
          source = None
          if loader is not None and hasattr(loader, "get_source"):
              try:
                  source = loader.get_source(module_name)
              except ImportError:
                  pass
              if source is not None:
                  source = source.splitlines()
          if source is None:
              try:
                  with open(filename, 'rb') as fp:
                      source = fp.read().splitlines()
              except (OSError, IOError):
                  pass
          if source is None:
              return None, [], None, []
  
          # If we just read the source from a file, or if the loader did not
          # apply tokenize.detect_encoding to decode the source into a Unicode
          # string, then we should do that ourselves.
          if isinstance(source[0], six.binary_type):
              encoding = 'ascii'
              for line in source[:2]:
                  # File coding may be specified. Match pattern from PEP-263
                  # (http://www.python.org/dev/peps/pep-0263/)
                  match = re.search(br'coding[:=]\s*([-\w.]+)', line)
                  if match:
                      encoding = match.group(1).decode('ascii')
                      break
              source = [six.text_type(sline, encoding, 'replace') for sline in source]
  
          lower_bound = max(0, lineno - context_lines)
          upper_bound = lineno + context_lines
  
          pre_context = source[lower_bound:lineno]
          context_line = source[lineno]
          post_context = source[lineno + 1:upper_bound]
  
          return lower_bound, pre_context, context_line, post_context