根据参数选择子类
我有一个模块(db.py),它可以从不同类型的数据库(比如sqlite、mysql等)加载数据。这个模块里面有一个叫做db_loader的类,还有一些子类(sqlite_loader、mysql_loader),这些子类都是从db_loader继承过来的。
使用的数据库类型是在一个单独的参数文件里定义的。
那么用户要怎么得到正确的对象呢?
也就是说,我该怎么写:
loader = db.loader()
我是在db.py模块里用一个叫做loader的方法,还是有更优雅的方法,让一个类根据参数自动选择它的子类?有没有什么标准的方法来处理这种情况?
4 个回答
我会把子类的名字存储在参数文件里,然后用一个工厂方法来根据这个名字创建相应的类实例:
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'))
有没有更优雅的方法让一个类根据参数选择自己的子类呢?
你可以通过重写基类的 __new__
方法来实现这个功能。这样,你只需要写 loader = db_loader(db_type)
,loader
就会“自动”变成适合数据库类型的正确子类。这个方法比其他答案稍微复杂一点,但在我看来,它绝对是最优雅的。
最简单的形式如下:
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'>
(注意,只要 __new__
返回的是 cls
的实例,实例的 __init__
方法会自动被调用。)
不过,这个简单的版本有一些问题,需要根据你的需求进行扩展和调整。最明显的是,你可能需要处理以下内容:
Parent(3) # KeyError
Child1(1) # KeyError
所以我建议要么把 cls
加入到 subclass_map
中,要么把它作为默认值使用,比如 subclass_map.get(feature, cls)
。如果你的基类不打算被实例化——也许它甚至有抽象方法?——那么我建议给 Parent
设置元类 abc.ABCMeta
。
如果你还有孙子类的话,我建议把收集子类的过程放到一个递归的类方法中,这样可以追溯每个继承关系,添加所有的后代。
在我看来,这个解决方案比工厂方法模式更美观。而且与其他一些答案不同的是,它是自我维护的,因为子类列表是动态创建的,而不是硬编码的映射。而且这个方法只会实例化子类,不像其他一些答案那样,会实例化全局命名空间中与给定参数匹配的任何东西。
听起来你想要使用 工厂模式。你需要定义一个工厂方法(可以在你的模块里,或者在一个所有可以生成对象的公共父类中),然后把参数传给这个方法,它就会返回正确类的实例。在Python中,这个问题比维基百科文章中描述的要简单一些,因为你的类型是动态的。
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):
...