我有一些看起来像这样的课程:
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]
,但我不喜欢以这种方式拆分我的类。
编辑:添加了有关订单的信息
答案 0 :(得分:12)
编写一个装饰器,将其添加到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
答案 1 :(得分:12)
这里基本上是@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.register_subclass()
注册的任何类名一起使用,无论是否它实际上是Base
的子类(虽然可以更改/修复。)
我指出这一点有几个原因:首先是因为在你的评论中你说subs
是一个列表中的“一堆类,其中一些可能是它的子类”,更重要的是,因为不我的更新中的代码的情况,这只适用于Base
子类,因为它们实际上通过元类自动“注册” - 但会在列表中留下任何其他内容单独。这可以被视为错误或功能。 ;¬)
答案 2 :(得分: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
答案 3 :(得分:3)
我很确定这对你有用。之后只需分配依赖的类属性。 这也不那么复杂了。
class Base:pass
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
Base.subs = [Sub3,Sub1]
print(Sub1.subs)
#[<class __main__.Sub3 at 0x0282B2D0>, <class __main__.Sub1 at 0x01C79810>]
答案 4 :(得分:3)
没有办法在Python中直接声明前向引用,但有几种解决方法,其中一些是合理的:
1)在定义子类后手动添加它们。
- Pros: easy to do; Base.subs is updated in one place
- Cons: easy to forget (plus you don't want to do it this way)
示例:
class Base(object):
pass
class Sub1(Base):
pass
class Sub2(Base):
pass
class Sub3(Base):
pass
Base.subs = [sub3, sub1]
2)使用Base.subs
值创建str
,并使用class decorator
替换实际的子类(这可以是Base上的类方法或者function - 我正在显示函数版本,虽然我可能会使用方法版本。)
- Pros: easy to do
- Cons: somewhat easy to forget;
示例:
def register_with_Base(cls):
name = cls.__name__
index = Base.subs.index(name)
Base.subs[index] = cls
return cls
class Base(object):
subs = ['Sub3', 'Sub1']
@register_with_Base
class Sub1(Base):
pass
class Sub2(Base):
pass
@register_with_Base
class Sub3(Base):
pass
3 )使用Base.subs
值创建str
,并让使用Base.subs
的方法进行替换。
- Pros: no extra work in decorators, no forgetting to update `subs` later
- Cons: small amount of extra work when accessing `subs`
示例:
class Base(object):
subs = ['Sub3', 'Sub1']
def select_sub(self, criteria):
for sub in self.subs:
sub = globals()[sub]
if #sub matches criteria#:
break
else:
# use a default, raise an exception, whatever
# use sub, which is the class defined below
class Sub1(Base):
pass
class Sub2(Base):
pass
class Sub3(Base):
pass
我会自己使用选项 3 ,因为它将功能和数据保存在一个地方。你唯一要做的就是让subs
保持最新状态(当然也要编写适当的子类)。
答案 5 :(得分:1)
我只是将子类定义为字符串,让不可避免的装饰器用它们命名的类替换字符串。我还要在元类上定义装饰器,因为我认为这更符合目标:我们正在修改类行为,就像你通过修改它的类来修改对象行为一样,你通过修改它的元类来修改类行为。 p>
class BaseMeta(type):
def register(cls, subcls):
try:
i = cls.subs.index(subcls.__name__)
except ValueError:
pass
else:
cls.subs[i] = subcls
finally:
return cls
class Base(object):
__metaclass__ = BaseMeta
subs = ['Sub3', 'Sub1']
@Base.register
class Sub1(Base): pass
@Base.register
class Sub2(Base): pass
@Base.register
class Sub3(Base): pass
print Base.subs
输出:
[<class '__main__.Sub3'>, <class '__main__.Sub1'>]
答案 6 :(得分:0)
class Foo:
pass
class Bar:
pass
Foo.m = Bar()
Bar.m = Foo()
答案 7 :(得分:0)
有一个解决方法:将引用的虚拟Sub1,Sub3类放在顶部,这用作“转发声明”。执行时,它们将被具有相同名称的实际实现替换。
forward-declaration.py:
class Sub1():
print("Sub1 dummy class called")
pass
class Sub3():
print("Sub3 dummy class called")
pass
class Base:
subs = [Sub3, Sub1]
print("Base class called")
class Sub1(Base):
print("Sub1 class called")
def __init__(self):
print("Sub1:__init__ called")
pass
class Sub2(Base):
def __init__(self):
print("Sub2:__init__ called")
pass
class Sub3(Base):
def __init__(self):
print("Sub3:__init__ called")
pass
sub_1 = Sub1()
sub_2 = Sub2()
sub_3 = Sub3()
print(Base.subs)
python forward-declaration.py
Sub1 dummy class called
Sub3 dummy class called
Base class called
Sub1 class called
Sub1:__init__ called
Sub2:__init__ called
Sub3:__init__ called
[<class '__main__.Sub3'>, <class '__main__.Sub1'>]
注意:上面的方法在mypy或pylint静态检查上失败,但是可以正常工作