【问题标题】:How to find most recent common ancestor (base type) of several types in python?如何在python中找到几种类型的最新共同祖先(基本类型)?
【发布时间】:2020-02-05 23:08:27
【问题描述】:

我需要找到一组类的最后一个共同祖先,以便返回该类型。

上下文,我正在做一些相当复杂的元编程,涉及重载 numpy 功能。 (不要问)我有一个函数的可变数量的参数,我使用这些信息提取到一个集合中的类型(过滤掉一些不相关的类型)我需要找出最远的类型树其中所有类型共享相同的基本类型。我有一些第一次尝试,但我被多重继承等绊倒了。

第一关:

def lca_type(types):
    if len(types) == 1:
        return types.pop()
    filtered_types = set()
    for type in types:
        if not any(issubclass(type, x) for x in types):
            filtered_types.add(type)
    if len(filtered_types) == 1:
        return filtered_types.pop()
    # TODO: things get tricky here

考虑以下类层次结构:

class A(object):
    pass
class B(A):
    pass
class C(A):
    pass
class D(A):
    pass
class B1(B):
    pass
class B2(B):
    pass
class BC(C, B1):
    pass
class CD(C, D):
    pass
class D1(D):
    pass
class BD(B, D):
    pass
class B1_1(B1):
    pass

预期结果:

lca_type({A,BD}) == A
lca_type({C}) == C
lca_type({B1,B2}) == B
lca_type({{B1_1, D}) == A
lca_type({CD, BD}) == D
lca_type({B1_1, BC}) == B1

【问题讨论】:

  • 可能有一个递归解决方案。我同意多重继承会使它复杂化,你需要做一个 BFS 来沿着任何分支找到 MRCA。
  • @barmer 我认为这是真的。

标签: python oop inheritance types casting


【解决方案1】:

您可以使用每个类的mro 方法来获取祖先类的列表。将祖先列表映射到collections.Counter,这样您就可以在它们上使用& 运算符来获取共同祖先,同时保持键顺序,有效模拟获取有序集合的交集。然后从聚合的Counter对象的键中对祖先序列使用next函数来获取最近的祖先:

from functools import reduce
from operator import and_
from collections import Counter

def lca_type(classes):
    return next(iter(reduce(and_, (Counter(cls.mro()) for cls in classes))))

所以下面的表达式都是True:

lca_type({A, BD}) == A
lca_type({C}) == C
lca_type({B1, B2}) == B
lca_type({B1_1, D}) == A
lca_type({CD, BD}) == D
lca_type({B1_1, BC}) == B1
lca_type({B1_1, BC, BD}) == B

请注意,键顺序仅针对 Python 3.7+ 维护,因此对于以前的 Python 版本,您可以将 Counter 替换为 collections.OrderedDict 的子类,该子类重用 Counter 的属性:

from functools import reduce
from operator import and_
from collections import Counter, OrderedDict
import collections

collections.Counter = Counter = type('Counter', (OrderedDict,), dict(vars(Counter)))

def lca_type(classes):
    return next(iter(reduce(and_, (Counter(cls.mro()) for cls in classes))))

【讨论】:

  • Here's an example lca_type 实现无法识别最近的共同祖先。
  • 您的新实现似乎没有做任何事情来确保它选择最近的共同祖先,而不是任何共同祖先。
  • 确实如此。通过使用next 函数,它从and-聚合Counter 对象的键序列中获取最近的祖先。查看演示:repl.it/@blhsing/PleasingAccurateCharmap
  • @blhsing:旁注:我也倾向于使用mro()(因为直接访问 dunder specials通常是错误的,您应该在某种方式),但在检查文档时,mro 方法基本上是元类修改 MRO 的钩子;它被调用一次,结果缓存在类的__mro__ 中。因此,为了提高性能,您需要使用 cls.__mro__ 而不是 cls.mro(),这样当缓存的 MRO 已经可用时,您就不会重新生成 MRO。
  • 另外,只是为了好玩,因为你已经在使用operatorfunctools,你可以用return next(iter(reduce(and_, map(Counter, map(attrgetter('__mro__'), classes))))) 将几乎整个循环推送到C 层。我说差不多,因为Counter 本身是用 Python 实现的(尽管它使用 C 加速器来加速计算输入可迭代)。不,我并不是真的想声称一对maps 比genexpr 更好,只是为了从字节码解释器中推出更多工作而获得乐趣。 :-)
【解决方案2】:

获取所有共同祖先的集合,使用朴素的最小算法找到最低的共同祖先,然后验证它实际上是最近的:

def lca_type(types):
    mros = [t.__mro__ for t in types]
    all_common_ancestors = set(mros[0]).intersection(*mros[1:])

    ancestor_iter = iter(all_common_ancestors)
    candidate = next(ancestor_iter)
    for next_candidate in ancestor_iter:
        if issubclass(next_candidate, candidate):
            candidate = next_candidate

    if all(issubclass(candidate, ancestor) for ancestor in all_common_ancestors):
        return candidate
    else:
        raise ValueError("No unambiguous lowest common ancestor")

使用简单的最小值可能看起来有问题,但实际上它很好,即使在多重继承中也是如此。如果有一个明确的最低共同祖先,那么它是all_common_ancestors 的每个元素(包括它自己)的子类,所以当我们到达它时,我们会将candidate 设置为最低共同祖先,然后再也不会改变candidate .如果没有明确的最低共同祖先,那么无论 candidate 在循环结束时结束,它都不会通过验证检查。

更棘手的部分是处理__subclasscheck__/__subclasshook__ 覆盖。我认为这个实现应该足够健壮,可以处理大多数常见的ABC案例,但是当任意__subclasscheck__实现定义图形时,最低共同祖先的整个概念变得很奇怪issubclass 甚至不是 DAG。

【讨论】:

  • 不推送这个(reduce 经常被不赞成),但你可以将所有代码从ancestor_iter 的定义替换到for 的末尾使用candidate = functools.reduce(lambda x, y: x if issubclass(x, y) else y, all_common_ancestors) 循环(无需import 并在Python 2 上使用functools 限定)。将值流浓缩为单个赢家是reduce 的几个典型用例之一,在这种情况下,它是冗长的一大胜利(如果不一定对那些不了解reduce 的人的可读性) )。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多