类的前向声明?
我有一些类看起来像这样:类的前向声明?
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
...
现在,这是因为Sub1的和Sub3中时Base.subs是没有定义失败。但显然我不能在Base之前放置子类。有没有办法在Python中转发声明类?我想与isinstance
一起工作,因此subs中的类型实际上必须与后面声明的子类相同,它们具有相同的名称和其他属性是不够的。
一种解决方法是:Base.subs = [Sub3,Sub1]
之后的子类已被定义,但我不喜欢必须以这种方式拆分我的类。
编辑:约为了
这里的本质@Ignacio巴斯克斯 - 艾布拉姆斯和它保留在列表中的子类的顺序@ 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
子类,因为它们实际上通过元类自动“注册” - 但会在列表中单独留下任何其他内容。这可以被视为一个错误或功能。
编辑补充信息:由于订单的增加要求的我完全重新设计我的答案。我也使用了一个类装饰器,这个在这里首先由@Ignacio Vazquez-Abrams使用。
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
在这种情况下,很难确定列表Base.subs的顺序。这取决于我编写易出错的代码的顺序。另外,Base.subs是Base类的一个属性,所以我宁愿在那里定义它,而不是将它分散到几个类中。 – pafcu 2010-11-12 07:36:07
是的,如果有人没有意识到阶级秩序很重要(通常不会),代码就会以有趣的方式突破。例如,当意外改变订单时很容易。重构或清理代码。 – pafcu 2010-11-12 07:46:26
我还没有看到它**应该**的问题。也许你应该考虑:a)在生成和维护秩序方面更加明确,或者b)不依赖于秩序。为什么它很重要? – knitti 2010-11-12 07:59:38
写一个装饰,它添加到注册表中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
对不起,忘了指定顺序是重要的(这是一个“首选”类型的列表)。这取决于代码的顺序是非常危险的。我想我可以添加一个参数给addsub,但是。尽管Base的内容仍然存在问题。潜艇遍布各处。 – pafcu 2010-11-12 07:39:47
class Foo:
pass
class Bar:
pass
Foo.m = Bar()
Bar.m = Foo()
这在问题中被排除了。 Foo的定义是分裂的。 – Oddthinking 2010-11-12 07:35:45
不,这不是... – 2010-11-12 07:36:54
我也只是定义了子类为字符串,并有必然的装饰与他们的名字的类更换琴弦。我还会在元类上定义装饰器,因为我认为这更符合目标:我们修改类行为,就像通过修改类修改对象行为一样,通过修改元类来修改类行为。
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'>]
这看起来像是一个非常有趣的方法,因为我可以在一个地方指定base.subs的所有元素。如果需要,可以更容易地进行更改。 – pafcu 2010-11-12 09:13:46
在这里使用元类有什么意义? – martineau 2010-11-13 00:01:12
@martineau,重点是我们正在改变班级的行为。通过改变类本身来做到这一点本质上是猴子修补一个对象。改变类的理由是改变其实例的行为 - 而不是改变自己的行为。你通过改变_it_是一个实例的类,即它的元类来做到这一点。我们不再用C++编程,类也是对象:)另外,我删除了对你答案的评论。出于某种原因,我并没有真正看到针对我的评论。 – aaronasterling 2010-11-13 08:39:59
我很确定这应该适合你。之后只需指定依赖的类属性。 这也不复杂。
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>]
该OP特别提到他/她为什么寻找另一种方式来做到这一点。 – martineau 2013-01-04 21:03:26
没有一个很好的理由,不必要地使事情变得复杂是我能想到的最反pythonic行为:-) – 2016-02-26 20:28:30
没有办法直接宣布在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
来代替实际的亚类(这可以是在基类方法或函数 - I显示功能版本,但我可能会使用方法版本)。
- 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
)创建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
我会用选项我自己,因为它使功能性和数据在同一个地方。你唯一需要做的就是保持subs
为最新版本(当然写下适当的子类)。
这是一个糟糕的设计。你正在将一个工厂(它知道子类)与超类(不需要知道子类)混为一谈。为什么要这样做?为什么不简单地分离事物并使事情更简单? – 2010-11-12 11:03:38
@ S.Lott:如果它本身并不需要知道它的子类本身。它只需要在列表中有一堆类,其中一些可能是它的子类。 – pafcu 2010-11-12 11:44:04
@pafcu:“其中一些可能是它的子类”。这仍然是一个坏主意 - 超类不应该知道它的子类。这样做违反了面向对象设计的原则。如果不更改超类,则不能再简单地创建新的子类。你必须从超类中分离出“类的列表”。拥有“班级列表”的唯一原因是创建工厂。工厂是一件好事;但它不是超类的特征。 – 2010-11-12 13:13:58