代码之家  ›  专栏  ›  技术社区  ›  noio

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

  •  4
  • noio  · 技术社区  · 14 年前

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

    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
    

    这显然行不通。但是有什么像 @classmethod 为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__ 然后 filter() CLSID 但这只适用于直接子类。

    所以,希望我能解释我的目的,问题是如何使这个工作?或者我这样做是完全错误的?

    2 回复  |  直到 14 年前
        1
  •  6
  •   Roger Pate    14 年前

    不可撤销地将其与基类绑定:

    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"
    
        2
  •  3
  •   Bruno Oliveira    14 年前

    你可以用元类来完成这项工作,但我认为一个简单的解决方案就足够了:

    class BaseClass(object):
        CLASS_ID = None
        _CLASSES = {}
    
        @classmethod
        def create_from_id(cls, class_id):
            return CLASSES[class_id]()
    
        @classmethod
        def register(cls):
            assert cls.CLASS_ID is not None, "subclass %s must define a CLASS_ID" % cls
            cls._CLASSES[cls.CLASS_ID] = cls
    

    然后定义一个子类,只需使用:

    class Foo(BaseClass):
        CLASS_ID = 'foo'
    
    Foo.register()
    

    最后使用基类中的factory方法为您创建实例:

    foo = BaseClass.create_from_id('foo')
    

    在这个解决方案中,在类定义之后,必须调用register class方法将子类注册到基类中。此外,默认 CLASS_ID 如果用户忘记定义基类,则为“无”以避免覆盖注册表中的基类。