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 becauseEntityBase.__subclasses__()
doesn't contain theSmth
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.
精彩评论