def descendants(obj, refattrs=(SEGMENTATION, ALIGNMENT), follow='first'):
    """
    >>> for des in query.descendants(igt.get_item('p1'), refattrs=(SEGMENTATION, ALIGNMENT)):
    ...     print(des)
    (<Tier object (id: p type: phrases) at ...>, 'segmentation', <Tier object (id: w type: words) at ...>, [<Item object (id: w1) at ...>])
    (<Tier object (id: p type: phrases) at ...>, 'alignment', <Tier object (id: t type: translations) at ...>, [<Item object (id: t1) at ...>])
    (<Tier object (id: w type: words) at ...>, 'segmentation', <Tier object (id: m type: morphemes) at ...>, [<Item object (id: m1) at ...>])
    (<Tier object (id: m type: morphemes) at ...>, 'alignment', <Tier object (id: g type: glosses) at ...>, [<Item object (id: g1) at ...>])
    """

    if hasattr(obj, 'tier'):
        tier = obj.tier
        items = [obj]
    else:
        tier = obj
        items = tier.items
    igt = tier.igt
    visited = set()
    agenda = deque([(tier, items)])
    while agenda:
        tier, items = agenda.popleft()
        tier_refs = tier.referrers(refattrs)
        item_ids = set(item.id for item in items)
        # get followable refattrs with something on the referrers list
        ras = [ra for ra in refattrs if tier_refs[ra]]
        if follow == 'first' and ras:
            ras = [ras[0]]
        if not ras:
            continue
        # unlike ancestors, descendants for a refattr may have 1+ tiers
        for refattr in ras:
            # try to avoid cycles
            if (tier.id, refattr) in visited:
                continue
            else:
                visited.add((tier.id, refattr))
            for reftier_id in tier_refs[refattr]:
                reftier = igt[reftier_id]
                refitems = [
                    item for item in reftier.items
                    if set(ref.ids(item.attributes.get(refattr,'')))
                       .intersection(item_ids)
                ]
                yield (tier, refattr, reftier, refitems)
                agenda.append((reftier, refitems))
def descendants ( obj , refattrs = ( SEGMENTATION , ALIGNMENT ) , follow = 'first' ) : if hasattr ( obj , 'tier' ) : tier = obj . tier items = [ obj ] else : tier = obj items = tier . items igt = tier . igt visited = set ( ) agenda = deque ( [ ( tier , items ) ] ) while agenda : tier , items = agenda . popleft ( ) tier_refs = tier . referrers ( refattrs ) item_ids = set ( item . id for item in items ) ras = [ ra for ra in refattrs if tier_refs [ ra ] ] if follow == 'first' and ras : ras = [ ras [ 0 ] ] if not ras : continue for refattr in ras : if ( tier . id , refattr ) in visited : continue else : visited . add ( ( tier . id , refattr ) ) for reftier_id in tier_refs [ refattr ] : reftier = igt [ reftier_id ] refitems = [ item for item in reftier . items if set ( ref . ids ( item . attributes . get ( refattr , '' ) ) ) . intersection ( item_ids ) ] yield ( tier , refattr , reftier , refitems ) agenda . append ( ( reftier , refitems ) )
>>> for des in query.descendants(igt.get_item('p1'), refattrs=(SEGMENTATION, ALIGNMENT)):
    ...     print(des)
    (<Tier object (id: p type: phrases) at ...>, 'segmentation', <Tier object (id: w type: words) at ...>, [<Item object (id: w1) at ...>])
    (<Tier object (id: p type: phrases) at ...>, 'alignment', <Tier object (id: t type: translations) at ...>, [<Item object (id: t1) at ...>])
    (<Tier object (id: w type: words) at ...>, 'segmentation', <Tier object (id: m type: morphemes) at ...>, [<Item object (id: m1) at ...>])
    (<Tier object (id: m type: morphemes) at ...>, 'alignment', <Tier object (id: g type: glosses) at ...>, [<Item object (id: g1) at ...>])