Динамический базовый класс и фабрики - PullRequest
2 голосов
/ 24 сентября 2010

У меня есть следующий код:

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 ]

9 голосов
/ 24 сентября 2010

Я бы сделал это с декоратором. Кроме того, хранение карты сущности -> подкласса в словаре позволяет заменить линейное сканирование поиском в режиме dict.

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)

Декоратор более изящен и позволяет нам чувствовать себя хорошо и модно, но словарь прост, полезен и по существу. Это легко понять и сложно ошибиться. Если вам действительно не нужно сделать что-то волшебное, тогда я думаю, что это путь. В любом случае, почему вы хотите это сделать?

5 голосов
/ 24 сентября 2010

Я думаю, что это один из немногих случаев, когда вам нужен Python метакласс :

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__.
3 голосов
/ 24 сентября 2010

Метакласс может отслеживать определенные классы.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'>

Кстати, фабрика создает экземпляры классов, поэтому имя не подходит для функции, которая возвращает только класс.

0 голосов
/ 24 сентября 2010

это верный подход или может я что-то не так понял и делаю не так?

Это работает. Так что в каком-то смысле это «верно».

Это полная трата кода. Так что в каком-то смысле это не «верно».

Нет никаких вариантов использования для этого вида конструкции. Теперь, когда вы его построили, вы можете перейти к решению практических задач.

...