【问题标题】:Greatest Common Superclass最大共同超类
【发布时间】:2014-11-05 08:24:54
【问题描述】:

是否有任何简单的方法来获取对象列表的“最大公共超类”?例如,如果

class A(object): pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(C): pass
class F(D, E): pass

b = B()
d = D()
e = E()

然后

gcs(b, e) is A
gcs(d, e) is C
gcs(e, e) is E

【问题讨论】:

    标签: python superclass


    【解决方案1】:

    我认为这里的一个问题是您可以拥有多个最大的公共超类——采用类继承结构,例如

    AB ---> A
       \ /
        x
       / \
    BA ---> B
    

    在这种情况下,AB 都是 gca([AB, BA]) 的合法答案。

    在我自己的代码中遇到这个问题后,我意识到我需要将问题重新定义为:

    找到指定类列表共享的最小公共基集;定义为所有类,它们 1) 是原始列表中所有内容的父类,并且 2) 没有子类也包含在返回的列表中。

    最后一项要求只获取“最近”类,但处理存在多个此类值的情况。

    以下代码实现了这一点:

    def minimal_common_bases(classes):
        # pull first class, and start with it's bases
        gen = iter(classes)
        cls = next(gen, None)
        if cls is None:
            return set()
        common = set(cls.__mro__)
    
        # find set of ALL ancestor classes,
        # by intersecting MROs of all specified classes
        for cls in gen:
            common.intersection_update(cls.__mro__)
    
        # remove any bases which have at least one subclass also in the set,
        # as they aren't part of "minimal" set of common ancestors.
        result = common.copy()
        for cls in common:
            if cls in result:
                result.difference_update(cls.__mro__[1:])
    
        # return set
        return result
    
    • 注意:在 py2 下,您需要使用 inspect.getmro(cls) 而不是 cls.__mro__

    【讨论】:

      【解决方案2】:

      这本质上是一个简化的最长公共子序列问题;比较 MRO 序列并返回其索引总和最小的类型:

      def gcs(a, b):
          """Find the common base class between two classes or instances"""
          try:
              a, b = a.mro(), b.mro()
          except AttributeError:
              a, b = type(a).mro(), type(b).mro()
          a_idx, b_idx = {t: i for i, t in enumerate(a)}, {t: i for i, t in enumerate(b)}
          try:
              return min(a_idx.viewkeys() & b_idx.viewkeys(),
                         key=lambda t: a_idx[t] + b_idx[t])
          except ValueError:
              return None
      

      这是一个 O(M+N) 算法,其中 M 和 N 是两个对象的 MRO 的大小。该函数可以处理类和实例。

      使用您的示例对象进行演示:

      >>> gcs(e, b)
      <class '__main__.A'>
      >>> gcs(e, d)
      <class '__main__.C'>
      >>> gcs(e, e)
      <class '__main__.E'>
      

      【讨论】:

      • 事实上,这让我觉得我的问题中有些东西没有那么明确,因为gcs 并不总是独一无二的......
      • 如果你想要“最大”的公共超类,你难道不想要 MRO 中的 last 通用元素吗?对于所有这些,这不是object吗?
      • 也许你可以称它为“最低”。我不确定在这种情况下正确的术语是什么。
      • @Bach:我给了你一个最大的通用超类;如果您想要“最低上方对象”,则必须返回 res[1](假设 res[0]object)。
      • @Martijn:我认为代码中可能存在错误。置换类的基类不应影响“最大的公共超类”。例如,如果 class D(C, B): pass 而不是 class D(B, C): pass,则 gse(d, e) 错误地返回 A 而不是 C
      【解决方案3】:

      基于 Martijn 的想法,但考虑到时间复杂度在这里不会成为问题,因此使用更简单的方法(感谢 @veedrac 的输入):

      def gcs(*instances):
          classes = [type(x).mro() for x in instances]
          for x in classes[0]:
              if all(x in mro for mro in classes):
                  return x
      
      print gcs(b, e)
      print gcs(d, e)
      print gcs(e, e)
      

      输出:

      <class '__main__.A'>
      <class '__main__.C'>
      <class '__main__.E'>
      

      上述代码使用集合的轻微变体:

      def gcs(*instances):
          mros = (type(ins).mro() for ins in instances)
          mro = next(mros)
          common = set(mro).intersection(*mros)
          return next((x for x in mro if x in common), None)
      

      【讨论】:

      • 为了使搜索更有效,可以使用二分搜索,因为 __mro__ 已排序。
      • @Vladimir 不,__mro__ 根本没有排序。 MRO 包含不同的类型,它们的比较可能会导致任意结果。您可以尝试比较sorted() 版本的__mro__ 和实际的__mro__ 来验证。虽然我添加了另一个使用集合的版本。
      • 在第二个示例中获取smallest 真的有任何意义吗?迭代mros[0] 可能更快,并且代码最终更干净,因为您可以解压缩。此外,您可能应该使用.mro() 而不是.__mro__
      • @Veedrac 好点,我认为第一个也不需要,因为新的 C3 算法确保始终保持单调性。我会更新我的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-25
      • 2021-12-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多