code: def _reconstruct(keyvals, dialect, keep_order=False,
                   sort_attribute_values=False):
      if not dialect:
          raise AttributeStringError()
      if not keyvals:
          return ""
      parts = []
      if constants.ignore_url_escape_characters or dialect['fmt'] != 'gff3':
          attributes = keyvals
      else:
          attributes = {}
          for k, v in keyvals.items():
              attributes[k] = []
              for i in v:
                  attributes[k].append(''.join([quoter[j] for j in i]))
      if dialect['repeated keys']:
          items = []
          for key, val in attributes.items():
              if len(val) > 1:
                  for v in val:
                      items.append((key, [v]))
              else:
                  items.append((key, val))
      else:
          items = list(attributes.items())
      def sort_key(x):
          try:
              return dialect['order'].index(x[0])
          except ValueError:
              return 1e6
      if keep_order:
          items.sort(key=sort_key)
      for key, val in items:
          if val:
              if sort_attribute_values:
                  val = sorted(val)
              val_str = dialect['multival separator'].join(val)
              if val_str:
                  if dialect['quoted GFF2 values']:
                      val_str = '"%s"' % val_str
                  part = dialect['keyval separator'].join([key, val_str])
          else:
              if dialect['fmt'] == 'gtf':
                  part = dialect['keyval separator'].join([key, '""'])
              else:
                  part = key
          parts.append(part)
      parts_str = dialect['field separator'].join(parts)
      if dialect['trailing semicolon']:
          parts_str += ';'
      return parts_str