views:

80

answers:

4

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 = 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?

A: 

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.

S.Lott
+3  A: 

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?

aaronasterling
Keep in mind that identifiers which begin and end with `__` are reserved for Python. No need to encourage bad habits! :-)
llasram
@llasram good looking out. all the cool kids do it and I forget sometimes.
aaronasterling
Thanks for you answers. I known about decorators and I have an implemented "factory stuff" on decorators. By this code I want to minimize "factory interface" from decorator + inheritance to inheritance only. I know I can implement it only on decorators, but if I extend class in decorator pylint show warnings about this extension.
W55tKQbuRu28Q4xv
@W55tk, is there any reason that the dictionary method is not good enough?
aaronasterling
Of course no - sometime someone forgot to add to dictionary a new entry
W55tKQbuRu28Q4xv
@W55tk I would say that that's their problem but if you want to do this fancy, then THC4k has a very nice metaclass that will do what you want.
aaronasterling
My factory should create objects by arbitrary string, not only by class name.
W55tKQbuRu28Q4xv
+1  A: 

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__.
llasram
Thanks for reply. By my code I want to minimize factory interface and I think Entity inheritance and _entity_ on different lines error prone and sometime someone forget to do it ;)
W55tKQbuRu28Q4xv
+1  A: 

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.

THC4k