类的前向声明?

67 投票
11 回答
63340 浏览
提问于 2025-04-16 06:57

我有一些类,看起来像这样:

class Base:
  subs = [Sub3,Sub1]
  # Note that this is NOT a list of all subclasses!
  # Order is also important

class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
...

现在,这个代码会出错,因为在定义Base.subs的时候,Sub1和Sub3还没有被定义。不过,显然我也不能把子类放在Base之前。请问在Python中有没有办法提前声明类?我想使用isinstance,所以subs中的类型实际上必须和后面定义的子类完全一致,仅仅名字和其他属性相同是不够的。

一个解决办法是:在子类定义之后,写Base.subs = [Sub3, Sub1],但我不喜欢这样把我的类分开。

编辑:添加了关于顺序的信息

11 个回答

4

编辑:因为增加了顺序的要求,我完全重新整理了我的回答。我还使用了一个类装饰器,这个概念最早是由@Ignacio Vazquez-Abrams提出的。

编辑2:代码现在经过测试,并且修正了一些小错误。


class Base(object):
    subs = []

    @classmethod
    def addsub(cls, before=None): 
        def inner(subclass):
            if before and before in cls.subs:
                cls.subs.insert(cls.subs.index(before), subclass)
            else:
                cls.subs.append(subclass)
            return subclass
        return inner

@Base.addsub()
class Sub1(Base):
    pass

class Sub2(Base):
    pass

@Base.addsub(before=Sub1)
class Sub3(Base):
    pass

14

写一个装饰器,把它添加到Base的注册表中。

class Base(object):
  subs = []

  @classmethod
  def addsub(cls, scls):
    cls.subs.append(scls)

 ...

@Base.addsub
class Sub1(Base):
  pass

class Sub2(Base):
  pass

@Base.addsub
class Sub3(Base):
  pass
26

这里有一个结合了@Ignacio Vazquez-Abrams和@aaronasterling两位的答案的混合版本,它能保持子类在列表中的顺序。最开始,我们手动把想要的子类名称(也就是字符串)放在subs列表中,然后每当定义一个子类时,一个类装饰器会把对应的字符串替换成实际的子类:

class Base(object):  # New-style class (i.e. explicitly derived from object).

    @classmethod
    def register_subclass(cls, subclass):
        """ Class decorator for registering subclasses. """

        # Replace any occurrences of the class name in the class' subs list.
        # with the class itself.
        # Assumes the classes in the list are all subclasses of this one.
        # Works because class decorators are called *after* the decorated class'
        # initial creation.
        while subclass.__name__ in cls.subs:
            cls.subs[cls.subs.index(subclass.__name__)] = subclass

        return cls  # Return modified class.

    subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.


@Base.register_subclass
class Sub1(Base): pass

@Base.register_subclass
class Sub2(Base): pass

@Base.register_subclass
class Sub3(Base): pass

print('Base.subs: {}'.format(Base.subs))
# Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>]

更新: metaclass(类的元类)

其实也可以用元类来做到同样的事情,这样的好处是你不需要像我之前的答案那样,逐个给每个子类加装饰器,而是让这一切自动发生。

需要注意的是,虽然元类的__init__()在创建每一个子类时都会被调用,但只有当子类的名字出现在subs列表中时,它才会更新这个列表。所以,最初的基础类对subs列表内容的定义仍然控制着里面的替换内容(保持顺序不变)。

class BaseMeta(type):

    def __init__(cls, name, bases, classdict):
        if classdict.get('__metaclass__') is not BaseMeta:  # Metaclass instance?
            # Replace any occurrences of a subclass' name in the class being
            # created the class' sub list with the subclass itself.
            # Names of classes which aren't direct subclasses will be ignored.
            while name in cls.subs:
                cls.subs[cls.subs.index(name)] = cls

        # Chain to __init__() of the class instance being created after changes.
        # Note class instance being defined must be new-style class.
        super(BaseMeta, cls).__init__(name, bases, classdict)


# Python 2 metaclass syntax.
class Base(object):  # New-style class (derived from built-in object class).
    __metaclass__ = BaseMeta
    subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.

# Python 3 metaclass syntax.
#class Base(metaclass=BaseMeta):
#    subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.


# Note: No need to manually register the (direct) subclasses.
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass

print('Base.subs: {}'.format(Base.subs))

输出:

Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>]

需要特别注意的是,这两个答案之间至少有一个微妙的区别——也就是说,第一个答案可以处理通过@Base.register_subclass()注册的任何类名,不管它是否真的是Base的子类(虽然这可能是可以更改或修复的)。

我提到这个是因为你在评论中说过subs是一个“类的列表,其中一些可能是它的子类”,更重要的是,因为在我更新的代码中并不是这样,它只适用于Base的子类,因为它们通过元类会自动“注册”——而其他的则会在列表中保持不变。这可以被视为一个bug或一个特性。;¬)

更新: Python 3.6+

在Python 3.6中,新增了一个object方法,叫做__init_subclass__(),它提供了一种更简单的实现方式,也不需要给所有子类加装饰器或定义元类:

#!/usr/bin/env python3.6

class Base:
    subs = ['Sub3', 'Sub1']  # Ordered list of subclass names.

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        while cls.__name__ in cls.subs:
            cls.subs[cls.subs.index(cls.__name__)] = cls


# Note: No need to manually register the subclasses.
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass

print('Base.subs: {}'.format(Base.subs))

撰写回答