docs: def project(self, attribute_list, named_attributes=None, force_primary_key=None):
          """
          derive a new heading by selecting, renaming, or computing attributes.
          In relational algebra these operators are known as project, rename, and extend.
          :param attribute_list:  the full list of existing attributes to include
          :param force_primary_key:  attributes to force to be converted to primary
          :param named_attributes:  dictionary of renamed attributes
          """
          try:  # check for missing attributes
              raise DataJointError('Attribute `%s` is not found' % next(a for a in attribute_list if a not in self.names))
          except StopIteration:
              if named_attributes is None:
                  named_attributes = {}
              if force_primary_key is None:
                  force_primary_key = set()
              rename_map = {v: k for k, v in named_attributes.items() if v in self.attributes}
  
              # copied and renamed attributes
              copy_attrs = (dict(self.attributes[k].todict(),
                                 in_key=self.attributes[k].in_key or k in force_primary_key,
                                 **({'name': rename_map[k], 'sql_expression': '`%s`' % k} if k in rename_map else {}))
                            for k in self.attributes if k in rename_map or k in attribute_list)
              compute_attrs = (dict(default_attribute_properties, name=new_name, sql_expression=expr)
                               for new_name, expr in named_attributes.items() if expr not in rename_map)
  
              return Heading(chain(copy_attrs, compute_attrs))