如何从类体中获取对当前类的引用?我想在基类中保存一个(所有子类,包括非直接子类)子类的字典,以便我可以根据字符串来实例化它们。我之所以这样做是因为 CLSID 是通过 Web 表单发送的,所以我希望将选择范围限制为从子类设置的选项。(我不想评估/全局化类名)。
class BaseClass(object):
CLSID = 'base'
CLASSES = {}
def from_string(str):
return CLASSES[str]()
class Foo(BaseClass):
CLSID = 'foo'
BaseClass.CLASSES[CLSID] = Foo
class Bar(BaseClass):
CLSID = 'bar'
BaseClass.CLASSES[CLSID] = Bar
显然行不通。但是有什么类似于 init 的类方法吗?这个类方法的想法是,每个类在读取时只运行一次并向基类注册该类。那么以下类似的内容就可以工作了:(还可以保存 Foo 和 Bar 中的额外行)
class BaseClass(object):
CLSID = 'base'
CLASSES = {}
@classmethod
def __init__(cls):
BaseClass.CLASSES[cls.CLSID] = cls
def from_string(str):
return CLASSES[str]()
我想过使用 subclasses 然后对 CLSID 进行过滤,但这只适用于直接子类。
因此,希望我已经解释了我的目的,问题是如何使这个工作?或者我完全错误地处理这件事?
- 解决方案
有两种方法可以解决这个问题:
方法一:
使用元类在类定义时自动注册子类。这可以通过以下代码实现:
class AutoRegister(type):
def __new__(mcs, name, bases, D):
self = type.__new__(mcs, name, bases, D)
if "ID" in D: # only register if has ID attribute directly
if self.ID in self._by_id:
raise ValueError("duplicate ID: %r" % self.ID)
self._by_id[self.ID] = self
return self
class Base(object):
__metaclass__ = AutoRegister
_by_id = {}
ID = "base"
@classmethod
def from_id(cls, id):
return cls._by_id[id]()
class A(Base):
ID = "A"
class B(Base):
ID = "B"
print Base.from_id("A")
print Base.from_id("B")
方法二:
使用一个独立的工厂类来管理子类的注册。这可以通过以下代码实现:
class IDFactory(object):
def __init__(self):
self._by_id = {}
def register(self, cls):
self._by_id[cls.ID] = cls
return cls
def __call__(self, id, *args, **kwds):
return self._by_id[id](*args, **kwds)
# could use a from_id function instead, as above
factory = IDFactory()
@factory.register
class Base(object):
ID = "base"
@factory.register
class A(Base):
ID = "A"
@factory.register
class B(Base):
ID = "B"
print factory("A")
print factory("B")
你可能会发现我更喜欢哪一个。与类层次结构完全分开,您可以轻松地扩展和修改,例如通过在两个名称下注册(使用 ID 属性只允许一个):
class IDFactory(object):
def __init__(self):
self._by_id = {}
def register(self, cls):
self._by_id[cls.ID] = cls
return cls
def register_as(self, name):
def wrapper(cls):
self._by_id[name] = cls
return cls
return wrapper
# ...
@factory.register_as("A") # doesn't require ID anymore
@factory.register # can still use ID, even mix and match
@factory.register_as("B") # imagine we got rid of B,
class A(object): # and A fulfills that roll now
ID = "A"
您还可以将工厂实例“保留”在基类中,同时保持它与基类分离:
class IDFactory(object):
#...
class Base(object):
factory = IDFactory()
@classmethod
def register(cls, subclass):
if subclass.ID in cls.factory:
raise ValueError("duplicate ID: %r" % subclass.ID)
cls.factory[subclass.ID] = subclass
return subclass
@Base.factory.register # still completely decoupled
# (it's an attribute of Base, but that can be easily
# changed without modifying the class A below)
@Base.register # alternatively more coupled, but possibly desired
class A(Base):
ID = "A"