python - 类的前向声明?

标签 python

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

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] 子类已定义,但我不喜欢拆分我的类以这种方式。

编辑:添加有关订单的信息

最佳答案

这基本上是@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'>]

更新:元类

同样的事情也可以使用元类来完成——它的优点是它消除了显式装饰每个子类的需要,如我上面的原始答案所示(你接受了),但是它使这一切自动发生。

请注意,即使调用元类的 __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 子类,因为它们有效地通过元类自动“注册”——但会将其他任何内容单独留在列表中。这可以被认为是一个错误或一个功能。 ;¬)

更新:Python 3.6+

在 Python 3.6 中添加了一个名为 __init_subclass__() 的新 object 方法。它提供了一种更简单的方式来实现不需要装饰所有子类或定义元类的东西:

#!/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))

关于python - 类的前向声明?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4162456/

相关文章:

python - Pandas:如何使用 groupby 和 apply() 标记列

python - 如何使用字符串操作在 python 中创建一个 n x n 的 X 框? (例如,替换、计数、查找、len 等)

python - WHERE 子句中带有倒置 Q 对象的子查询

python - Openpyxl 边界线在与另一条边界线的交点处中断

Python 和多处理示例

python - Windows 7 任务计划程序

python - 在python中生成唯一的随机二进制数组

python - 使用 pd.to_datetime 将 int 转换为 HHMMSS 格式

python - Python 中在不同线程中调用相同函数的最佳方式是什么?

python:当字符串包含多个零时,检查一串整数是否大于零不起作用