【问题标题】:How to get a reference to the current class from class body?如何从类体中获取对当前类的引用?
【发布时间】:2010-02-03 11:28:40
【问题描述】:

我想在基类中保留一个(所有,非立即包含的)子类的字典,以便我可以从字符串中实例化它们。我这样做是因为CLSID 是通过网络表单发送的,所以我想将选择限制为从子类中设置的选项。 (我不想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 这样的初始化?这个想法是这个类方法只会在每个类被读取并将类注册到基类时运行一次。像下面这样的东西可以工作:(也可以在FooBar中保存额外的行)

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

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

    def from_string(str):
        return CLASSES[str]()

我曾考虑在CLSID 上使用__subclasses__,然后使用filter(),但这仅适用于直接子类。

所以,希望我解释我的目的,问题是如何使这项工作?还是我的做法完全错误?

【问题讨论】:

  • 通常我们为此使用单独的工厂,而不是超类。为什么不使用更常见的 Factory 设计模式?
  • 我认为将工厂方法放在超类中会更干净。此外,我不想对类/ID 的选项进行硬编码,我希望任何子类也是自包含的,这样基类和工厂方法就可以保持为黑匣子。
  • 将工厂放在超类中远非干净 - 正如您的问题所揭示的那样。 “硬编码选项?”没有意义。工厂就像超类一样是“黑匣子”,所以我不明白这一点。
  • 我现在开始看到了 :)。我想在这里进行硬编码:en.wikipedia.org/wiki/Factory_method_pattern#ActionScript_3.0PizzaFactory 类。

标签: python inheritance class


【解决方案1】:

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

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】:

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

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()

最后使用 BaseClass 中的工厂方法为您创建实例:

foo = BaseClass.create_from_id('foo')

在这个解决方案中,在类定义之后必须调用注册类方法将子类注册到基类中。此外,默认的CLASS_ID 是 None 以避免在用户忘记定义时覆盖注册表中的基类。

【讨论】:

    猜你喜欢
    • 2016-02-14
    • 2013-08-04
    • 1970-01-01
    • 2017-07-22
    • 2011-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多