如何从类体中获取当前类的引用?

noi*_*oio 4 python inheritance class

我想在基类中保留(所有非立即包含的)子类的字典,以便我可以从字符串中实例化它们.我这样做CLSID是因为它是通过Web表单发送的,所以我想将选择限制为从子类设置的选项.(我不想eval()/ globals()classname).

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
Run Code Online (Sandbox Code Playgroud)

这显然不起作用.但是有没有像@classmethodinit 一样的东西?这个想法是这个类方法只会在读取每个类时运行一次,并使用基类注册该类.然后像下面的内容可以工作:(也可以节省额外的行FooBar)

class BaseClass(object):
    CLSID = 'base'
    CLASSES = {}

    @classmethod
    def __init__(cls):
        BaseClass.CLASSES[cls.CLSID] = cls 

    def from_string(str):
        return CLASSES[str]()
Run Code Online (Sandbox Code Playgroud)

我想过使用__subclasses__然后再filter()使用CLSID,但这只适用于直接的子类.

所以,希望我解释了我的目的,问题是如何使这项工作?或者我是以完全错误的方式解决这个问题?

小智 6

不可避免地将其与基类联系起来:

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")
Run Code Online (Sandbox Code Playgroud)

或者将不同的问题实际分开:

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")
Run Code Online (Sandbox Code Playgroud)

你可能已经选择了我喜欢哪一个.与类层次结构分开定义,可以轻松扩展和修改,例如通过注册两个名称(使用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"
Run Code Online (Sandbox Code Playgroud)

您还可以将工厂实例保持在基础"内部",同时保持其解耦:

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"
Run Code Online (Sandbox Code Playgroud)