类的前向声明?
我有一些类,看起来像这样:
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 个回答
编辑:因为增加了顺序的要求,我完全重新整理了我的回答。我还使用了一个类装饰器,这个概念最早是由@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
写一个装饰器,把它添加到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
这里有一个结合了@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))