按对象类型排序

1 投票
3 回答
1041 浏览
提问于 2025-04-15 23:16

我有一段代码,在模块加载时静态地注册了 (类型, 处理函数) 的配对,结果形成了一个字典,像这样:

HANDLERS = {
  str: HandleStr,
  int: HandleInt,
  ParentClass: HandleCustomParent,
  ChildClass: HandleCustomChild
  }

def HandleObject(obj):
  for data_type in sorted(HANDLERS.keys(), ???):
    if isinstance(obj, data_type):
      HANDLERS[data_type](obj)

这里的 ChildClass 是从 ParentClass 继承而来的。问题是,因为这是一个字典,所以里面的顺序是没有定义的——但是我想知道怎么查看类型对象,以便找出一个排序的关键。

最终的排序应该是子类在前,父类在后(最具体的类型在前面)。比如说,str 应该排在 basestring 前面,而 ChildClass 应该排在 ParentClass 前面。如果类型之间没有关系,那么它们的相对位置就无所谓了。

3 个回答

0

使用 Python 2.7 或 3.1 中的 collections.OrderedDict。这个东西是用纯 Python 写的,所以如果你需要的话,可以很容易地把它放到早期版本中使用或者进行调整。

OrderedDict 会保持你添加元素的顺序。

1

对每个类的 __bases__ 成员进行一个 拓扑排序

5

如果你知道自己总是处理新式类:

def numberofancestors(klass):
    return len(klass.mro())

或者,如果你担心可能会有旧式类混在里面:

import inspect

def numberofancestors(klass):
    return len(inspect.getmro(klass))

在这两种情况下,

sorted(HANDLERS, key=numberofancestors, reversed=True)

都会给你想要的结果(你不需要 .keys() 这一部分)。

@Ignacio 提出的拓扑排序在理论上是正确的,但考虑到给定一个类,你可以很容易且快速地得到它的前辈数量(也就是“祖先”...用这个词有点奇怪,因为你也是自己的祖先之一;-),使用这些 numberofancestors 函数,我的方法更实用:它依赖于一个显而易见的事实,即任何派生类比它的基类多至少一个“祖先”,因此,使用这个 key=,它总是会排在任何基类之前。

不相关的类可能会以任意顺序出现(就像在拓扑排序中一样),但你已经明确表示你对此不在乎。

编辑:原作者在后面的评论中思考关于多重继承的最佳支持,提出了一个与问题中“预排序”原始想法截然不同的主意,但他关于如何实现这个新想法的建议并不是最优的:

[h for h in [HANDLERS.get(c) for c in type(obj).mro()] if h is not None][0]

这个想法很好(如果多重继承支持对你有兴趣的话),但最好的实现方式可能是(Python 2.6 或更高版本):

next(Handlers[c] for c in type(obj).mro() if c in Handlers)

通常,adict.get(k) 并检查是否不为 Noneif k in adict: adict[k] 更快,但这并不是一个特别正常的情况,因为使用 get 需要构建一个“假”的单项列表并在其上“循环”以模拟赋值。

更一般来说,仅仅为了获取列表的 [0] 项而通过列表推导构建整个列表是多余的工作——在生成表达式上调用内置的 next 函数更像是 first,也就是说,“给我生成表达式的第一个项目”,并且没有额外的工作。如果列表推导/生成表达式是空的,它会引发 StopIteration 而不是 IndexError,但这通常不是问题;你还可以给 next 提供第二个参数,作为生成表达式为空时的“默认值”。

在 2.5 及更早版本中,你必须使用 (thegenexp).next()(而且没有办法给它提供默认参数),但虽然在语法上稍微不那么光鲜,但在语义和速度上与 2.6 及更高版本的构造基本等价。

我很高兴讨论在评论中继续,因为我认为这个得出的结论是有价值的,并且可能有用(尽管在原作者的应用环境中,多重继承可能并不是一个问题)。

撰写回答