开发者

Dynamic base class and factories

I have following code:

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开发者_JAVA百科 = factory ("SMTH") (1, 2)
print (s.a, s.b)
# <--

Now in factory I can get all subclasses of EntityBase, find concrete subclass for "SMTH" and create it.

Is this valid approach or maybe I something misunderstood and doing wrong?


I would do this with a decorator. Also, storing the entity -> subclass map in a dictionary lets you replace a linear scan with a dict lookup.

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)

I'm not sure if the __entity__ attribute is actually useful to you of if you were just using it to implement the linear scan. I left it in but if you took it out, then the classes associated with entity wouldn't even need to inherit from EntityBase and you could rename it to something like Registry. This shallows up your inheritance tree and opens the possibility of using on classes that aren't related through common descent.

Depending on what your use case is, a better way to do it might just be

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)

The decorator is fancier and let's us feel nice and fancy about ourselves but the dictionary is plain, useful and to the point. It is easy to understand and difficult to get wrong. Unless you really need to do something magic, then I think that that's the way to go. Why do you want to do this, anyway?


I think this is one of the few cases where you want a 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)

A few more subtle differences from your code:

  • There's no need to create a subclass with your entity() factory function then subclass that subclass. That approach not only creates more subclasses than necessary, but also makes your code not work because EntityBase.__subclasses__() doesn't contain the Smth class.
  • Identifiers which both begin and end with __ are reserved for Python, so I'm using the _entity_ attribute instead of __entity__.


A metaclass can keep track of the defined classes. Register.__init__ is called when a class with this metaclass is defined. We can just add the name and object to a registry dict in the metaclass. This way you can look it up directly later.

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'>

Btw, a factory instantiates classes, so the name is not fitting for a function that only returns a class.


this valid approach or maybe I something misunderstood and doing wrong?

It works. So in one sense it's "valid".

It's a complete waste of code. So in one sense it's not "valid".

There aren't any use cases for this kind of construct. Now that you've built it, you can move on to solving practical problems.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜