开发者

pick a subclass based on a parameter

开发者 https://www.devze.com 2023-04-02 00:27 出处:网络
I have a module开发者_运维技巧 (db.py) which loads data from different database types (sqlite,mysql etc..) the module contains a class db_loader and subclasses (sqlite_loader,mysql_loader) which inher

I have a module开发者_运维技巧 (db.py) which loads data from different database types (sqlite,mysql etc..) the module contains a class db_loader and subclasses (sqlite_loader,mysql_loader) which inherit from it.

The type of database being used is in a separate params file,

How does the user get the right object back?

i.e how do I do:

loader = db.loader()

Do I use a method called loader in the db.py module or is there a more elegant way whereby a class can pick its own subclass based on a parameter? Is there a standard way to do this kind of thing?


Sounds like you want the Factory Pattern. You define a factory method (either in your module, or perhaps in a common parent class for all the objects it can produce) that you pass the parameter to, and it will return an instance of the correct class. In python the problem is a bit simpler than perhaps some of the details on the wikipedia article as your types are dynamic.

class Animal(object):

    @staticmethod
    def get_animal_which_makes_noise(noise):
        if noise == 'meow':
            return Cat()
        elif noise == 'woof':
            return Dog()

class Cat(Animal):
    ...

class Dog(Animal):
    ...


is there a more elegant way whereby a class can pick its own subclass based on a parameter?

You can do this by overriding your base class's __new__ method. This will allow you to simply go loader = db_loader(db_type) and loader will magically be the correct subclass for the database type. This solution is mildly more complicated than the other answers, but IMHO it is surely the most elegant.

In its simplest form:

class Parent():
    def __new__(cls, feature):
        subclass_map = {subclass.feature: subclass for subclass in cls.__subclasses__()}
        subclass = subclass_map[feature]
        instance = super(Parent, subclass).__new__(subclass)
        return instance

class Child1(Parent):
    feature = 1

class Child2(Parent):
    feature = 2

type(Parent(1))  # <class '__main__.Child1'>
type(Parent(2))  # <class '__main__.Child2'>

(Note that as long as __new__ returns an instance of cls, the instance's __init__ method will automatically be called for you.)

This simple version has issues though and would need to be expanded upon and tailored to fit your desired behaviour. Most notably, this is something you'd probably want to address:

Parent(3)  # KeyError
Child1(1)  # KeyError

So I'd recommend either adding cls to subclass_map or using it as the default, like so subclass_map.get(feature, cls). If your base class isn't meant to be instantiated -- maybe it even has abstract methods? -- then I'd recommend giving Parent the metaclass abc.ABCMeta.

If you have grandchild classes too, then I'd recommend putting the gathering of subclasses into a recursive class method that follows each lineage to the end, adding all descendants.

This solution is more beautiful than the factory method pattern IMHO. And unlike some of the other answers, it's self-maintaining because the list of subclasses is created dynamically, instead of being kept in a hardcoded mapping. And this will only instantiate subclasses, unlike one of the other answers, which would instantiate anything in the global namespace matching the given parameter.


I'd store the name of the subclass in the params file, and have a factory method that would instantiate the class given its name:

class loader(object):
  @staticmethod
  def get_loader(name):
    return globals()[name]()

class sqlite_loader(loader): pass

class mysql_loader(loader): pass

print type(loader.get_loader('sqlite_loader'))
print type(loader.get_loader('mysql_loader'))


Store the classes in a dict, instantiate the correct one based on your param:

db_loaders = dict(sqlite=sqlite_loader, mysql=mysql_loader)
loader = db_loaders.get(db_type, default_loader)()

where db_type is the paramter you are switching on, and sqlite_loader and mysql_loader are the "loader" classes.

0

精彩评论

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