动态基类和工厂

2 投票
4 回答
5371 浏览
提问于 2025-04-16 04:33

我有以下代码:

class EntityBase (object) :
    __entity__ = None

    def __init__ (self) :
        pass

def entity (name) :
    class Entity (EntityBase) :
        __entity__ = name

        def __init__ (self) :
            pass

    return Entity

class Smth (entity ("SMTH")) :
    def __init__ (self, a, b) :
        self.a = a
        self.b = b

# added after few comments -->
def factory (tag) :
    for entity in EntityBase.__subclasses__ () :
        if entity.__entity__ == tag :
            return entity.__subclasses__ ()[0]

    raise FactoryError (tag, "Unknown entity")

s = factory ("SMTH") (1, 2)
print (s.a, s.b)
# <--

现在在工厂里,我可以获取所有的EntityBase子类,找到“SMTH”的具体子类并创建它。

这种方法有效吗?还是我有什么误解,做错了?

4 个回答

3

元类可以用来记录定义的类。当一个使用了这个元类的类被定义时,会调用Register.__init__。我们可以在元类中创建一个注册字典,把类的名字和对象添加进去。这样的话,以后就可以直接查找这些类了。

registry = {} # dict of subclasses

def get_entity( name ):
    return registry[name]    

class Register(type):
    def __init__(cls, name, bases, dict):
        registry[name] = cls
        type.__init__(cls,name, bases, dict)

class EntityBase(object):
    __metaclass__ = Register

class OneThing(EntityBase):
    pass

class OtherThing(OneThing):
    pass

print registry # dict with Entitybase, OneThing, OtherThing
print get_entity("OtherThing") # <class '__main__.OtherThing'>

顺便说一下,工厂是用来实例化类的,所以如果一个函数只是返回一个类,叫它工厂不太合适。

5

我觉得这是少数几个需要用到Python的 metaclass的情况之一:

class Entity(object):
    class __metaclass__(type):
        ENTITIES = {}

        def __new__(mcs, name, bases, cdict):
            cls = type.__new__(mcs, name, bases, cdict)
            try:
                entity = cdict['_entity_']
                mcs.ENTITIES[entity] = cls
            except KeyError:
                pass
            return cls

    @classmethod
    def factory(cls, name):
        return cls.__metaclass__.ENTITIES[name]

class Smth(Entity):
    _entity_ = 'SMTH'

    def __init__(self, a, b):
        self.a = a
        self.b = b

s = Entity.factory("SMTH")(1, 2)
print (s.a, s.b)

你代码中还有一些更细微的不同之处:

  • 你不需要先用你的entity()工厂函数创建一个子类,然后再从这个子类继续创建。这样做不仅会产生比必要更多的子类,还会导致你的代码无法正常工作,因为EntityBase.__subclasses__()里并不包含Smth类。
  • __开头和结尾的标识符是Python保留的,所以我用_entity_属性,而不是__entity__
9

我会用一个装饰器来实现这个功能。而且,把实体和子类的关系存储在一个字典里,可以让你用字典查找代替线性扫描,这样效率更高。

class EntityBase(object):
    _entity_ = None
    _entities_ = {}

    @classmethod
    def factory(cls, entity):
        try:
            return cls._entities_[entity]
        except KeyError:
            raise FactoryError(tag, "Unknown entity")

    @classmethod
    def register(cls, entity):
        def decorator(subclass):
            cls._entities_[entity] = subclass
            subclass._entity_ = entity
            return subclass
        return decorator

 factory = EntityBase.factory
 register = EntityBase.register

 @register('Smith')
 class Smith(EntityBase):
     def __init__(self, a, b):
         self.a = a
         self.b = b

 s = factory('Smith')(1, 2)

我不太确定__entity__这个属性对你是否真的有用,还是你只是用它来实现线性扫描。如果你把它去掉,那么和实体相关的类就不需要继承自EntityBase了,你可以把它改名为Registry。这样可以简化你的继承结构,也让你可以在一些没有共同祖先的类上使用。

根据你的使用场景,可能还有更好的做法,就是这样:

factory = {}

class Smith(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
factory['Smith'] = Smith

class Jones(object):
    def __init__(self, c, d):
         self.c = c
         self.d = d
factory['Jones'] = Jones

s = factory['Smith'](1, 2)
j = factory['Jones'](3, 4)

虽然装饰器看起来更高级,让我们觉得自己很厉害,但字典简单、实用,直接明了。它容易理解,也不容易出错。除非你真的需要做一些特别复杂的事情,否则我觉得用字典更合适。你为什么想要这样做呢?

撰写回答