Python 类的前向声明?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4162456/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Forward declaration of classes?
提问by pafcu
I have some classes looking like this:
我有一些看起来像这样的课程:
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
...
Now, this fails because Sub1 and Sub3 are not defined when Base.subs is. But obviously I can't put the subclasses before Base either. Is there a way to forward-declare classes in Python? I want to work with isinstanceso the types in subs actually have to be the same as the later declared subclasses, it's not enough that they have the same name and other properties.
现在,这失败了,因为在 Base.subs 时未定义 Sub1 和 Sub3。但显然我也不能将子类放在 Base 之前。有没有办法在 Python 中预先声明类?我想使用isinstance所以 subs 中的类型实际上必须与后来声明的子类相同,它们具有相同的名称和其他属性是不够的。
One workaround is to do: Base.subs = [Sub3,Sub1]afterthe subclasses have been defined, but I don't like having to split my class in that way.
一种解决方法是:Base.subs = [Sub3,Sub1]在定义子类之后,但我不喜欢以这种方式拆分我的类。
Edit: Added information about order
编辑:添加了有关订单的信息
采纳答案by martineau
Here's essentially a hybrid version of @Ignacio Vazquez-Abrams' and @aaronasterling's answers which preserves the order of the subclasses in the list. Initially the desired subclass names (i.e. strings) are manually placed in the subslist in the desired order, then as each subclass is defined, a class decorator causes the corresponding string to be replaced with the actual subclass:
这里本质上是@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'>]
Update
更新
Exactly the same thing can also be done using a metaclass—which has the advantage that it eliminates the need to explicitly decorate each subclass as shown in my original answer that's shown above (which you accepted), however it makes it all happen automagically. Note that even though the metaclass' __init__()is called for the creation of every subclass, it only updates the subslist if the subclass' name appears in it—so the initial Base class' definition of the contents of the subslist still controls what gets replaced in it (maintaining its order).
使用元类也可以完成完全相同的事情——它的优点是不需要显式装饰每个子类,如我上面显示的原始答案(您接受了)所示,但是它使这一切自动发生。请注意,即使元类'__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))
It important to note that there's at least one subtle difference between these two answers—namely that the the first will work with anyclass name that is registered via @Base.register_subclass(), whether or not its actually a subclass of Base(although that might be possible to change/fix.)
它重要的是要注意,有两个答案,即之间至少有一个细微的差别,与第一将工作任何经由注册类名@Base.register_subclass(),不管是不是其实际的子类Base(尽管这也许可以改变/修复.)
I'm pointing this out for a couple of reasons: First because in your comments you said that subswas a "bunch of classes in a list, some of which might be its subclasses", and more importantly, because that's notthe case with the code in my update, which only works for Basesubclasses since they effectively get "registered" automatically via the metaclass—but will leave anything else in the list alone. This could be considered a bug or a feature. ;?)
我指出这一点有几个原因:首先,因为在您的评论中,您说这subs是“列表中的一堆类,其中一些可能是它的子类”,更重要的是,因为情况并非如此我更新中的代码,它只适用于Base子类,因为它们有效地通过元类自动“注册”——但将单独保留列表中的任何其他内容。这可以被视为错误或功能。;?)
回答by knitti
Edit:Because of the added requirement of order I completely reworked my answer. I also make use of a class decorator, which was used here first by @Ignacio Vazquez-Abrams.
编辑:由于增加了订单要求,我完全修改了我的答案。我还使用了一个类装饰器,@Ignacio Vazquez-Abrams 在这里首先使用了它。
Edit2:code now tested and some stupidity slightly corrected
Edit2:现在测试了代码,一些愚蠢的地方稍微更正了
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
回答by Ignacio Vazquez-Abrams
Write a decorator that adds it to the registry in Base.
编写一个装饰器,将其添加到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
回答by demas
class Foo:
pass
class Bar:
pass
Foo.m = Bar()
Bar.m = Foo()
回答by aaronasterling
I would just define the subclasses as strings and have the inevitable decorator replace the strings with the classes that they name. I would also define the decorator on a metaclass because I think that that's more in line with the objective: we're modifying class behavior and just like you modify object behavior by modifying its class, you modify class behavior by modifying its metaclass.
我只是将子类定义为字符串,并让不可避免的装饰器用它们命名的类替换字符串。我还将在元类上定义装饰器,因为我认为这更符合目标:我们正在修改类行为,就像您通过修改其类来修改对象行为一样,您通过修改其元类来修改类行为。
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
This outputs:
这输出:
[<class '__main__.Sub3'>, <class '__main__.Sub1'>]
回答by user1761366
I'm pretty sure this should work for you. Just assign the depended class attribute afterwards. This is also a lot less complicated.
我很确定这应该对你有用。之后只需分配依赖的类属性。这也没有那么复杂。
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>]
回答by Ethan Furman
There is no way to directly declare forward-references in Python, but there are several workarounds, a few of which are reasonable:
在 Python 中无法直接声明前向引用,但有几种解决方法,其中一些是合理的:
1)Add the subclasses manually after they are defined.
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)
Example:
例子:
class Base(object):
pass
class Sub1(Base):
pass
class Sub2(Base):
pass
class Sub3(Base):
pass
Base.subs = [sub3, sub1]
2)Create Base.subswith strvalues, and use a class decoratorto substitute the actual subclasses (this can be a class method on Base or a function -- I'm showing the function version, although I would probably use the method version).
2)Base.subs使用str值创建,并使用 aclass decorator替换实际的子类(这可以是 Base 上的类方法或函数——我显示的是函数版本,尽管我可能会使用方法版本)。
- Pros: easy to do
- Cons: somewhat easy to forget;
Example:
例子:
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) Create Base.subswith strvalues, and have the method that uses Base.subsdo the substitution.
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`
Example:
例子:
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
I would use option 3myself, as it keeps the functionality and the data all in one place. The only thing you have to do is keep subsup to date (and write the appropriate subclasses, of course).
我自己会使用选项3,因为它将功能和数据都保存在一个地方。您唯一需要做的就是保持subs最新状态(当然,还要编写适当的子类)。
回答by Jonathan L
There is a workaround: put referenced dummy Sub1, Sub3 classes at the top, this works as "forward declaration". They will be replaced by the actual implementations with the same names when executed.
有一个解决方法:将引用的虚拟 Sub1、Sub3 类放在顶部,这用作“前向声明”。在执行时,它们将被具有相同名称的实际实现替换。
forward-declaration.py:
前向声明.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
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'>]
Note: The above method fails on mypy or pylint static check but works properly
注意:上述方法在 mypy 或 pylint 静态检查中失败,但工作正常

