Skip to content

Multipart features

Several GFF3 lines may share one ID — the standard way to represent a CDS interrupted by a frameshift, and how NCBI RefSeq encodes split CDS segments. Under mode="strict" those lines become one logical feature with a segments side table holding the physical lines.

MultipartFeature and FeatureSegment both subclass Feature and override none of __str__, __len__, __hash__, __eq__, __getitem__ or astuple — the compatibility surface is preserved by inaction. len() stays the envelope span; covered_length is the new quantity that excludes the gaps.

gffbase.feature.MultipartFeature

MultipartFeature(*args, n_segments: int = 1, segments=None, segment_loader=None, **kwargs)

Bases: Feature

A logical feature assembled from more than one input line.

Its own start/end are the ENVELOPE -- MIN(segment.start) and MAX(segment.end) -- and every inherited method operates on that envelope, so a caller that knows nothing about discontinuous features sees exactly the gffutils behaviour for a feature spanning that range.

Consequently len(f) is the envelope span, matching Feature. covered_length is the different, new quantity.

Source code in python/gffbase/feature.py
def __init__(self, *args, n_segments: int = 1, segments=None, segment_loader=None, **kwargs):
    super().__init__(*args, **kwargs)
    self.n_segments = n_segments
    self._segments = tuple(segments) if segments is not None else None
    # Called with no arguments to fetch this feature's segments, for the
    # case where they were not prefetched. `_yield_features` prefetches one
    # chunk at a time so the common path never calls this -- without that,
    # iterating a multipart corpus would be an N+1.
    self._segment_loader = segment_loader

covered_length property

covered_length: int

Total length actually covered, with the gaps excluded.

Differs from len(self), which is the envelope span. For a CDS split across two 100 bp exons 700 bp apart, len is 900 and this is 200.

Segments of a discontinuous feature must not overlap; if a malformed file provides overlapping ones, the shared bases are counted twice.

to_lines

to_lines(normalized: bool = False) -> list[str]

Every input line of this feature, in file order.

Source code in python/gffbase/feature.py
def to_lines(self, normalized: bool = False) -> list[str]:
    """Every input line of this feature, in file order."""
    return [seg.to_line(normalized) for seg in self.segments]

gffbase.feature.FeatureSegment

FeatureSegment(*args, seg_idx: int = 0, **kwargs)

Bases: Feature

One physical input line of a discontinuous feature.

Carries its OWN coordinates, score, phase and column 9 -- per-segment CDS phase is the main reason the storage exists -- while seqid, source, featuretype and strand come from the logical feature, which by definition shares them.

self.id is the LOGICAL id, so db[seg.id] finds the whole feature. The segment's own ID= is preserved byte-for-byte in the attributes blob, so str(segment) reproduces the input line exactly.

Source code in python/gffbase/feature.py
def __init__(self, *args, seg_idx: int = 0, **kwargs):
    super().__init__(*args, **kwargs)
    #: 0-based position in FILE order, not coordinate order. GFF3 does not
    #: require segments to be sorted, and `to_lines()` has to reproduce the
    #: input.
    self.seg_idx = seg_idx