在Python中能模拟Scala的特质吗?
我想创建一些轻量级的接口,这些接口里有一些方法,可以插入到类里面。下面是一个用Scala写的简单例子:
class DB {
def find(id: String) = ...
}
trait Transformation extends DB {
def transform(obj: String): String
override def find(id: String) =
transform(super.find(id))
}
trait Cache extends DB {
val cache = Cache()
override def find(id: String) = {
...
if (cache.contains(id))
cache.find(id)
else {
cache.set(id, super.find(id))
cache.get(id)
}
}
}
通过这些类(特性),我们可以创建带有转换功能、缓存功能,或者两者都有的数据库类。需要注意的是,转换功能里有一个抽象方法叫做transform,这个方法还需要在具体的类中实现。
new DB() with Transformation {
def transform(obj: String): obj.toLower()
}
new DB() with Cache
new DB() with Transformation with Cache {
def transform(obj: String): obj.toLower()
}
在Python中有没有办法做到类似的事情呢?我知道Python有一个叫做Traits的包,但它的目的似乎不太一样。
4 个回答
这是一个使用类的替代方案。如果你想在运行时创建特性(Traits),我们可以利用 type() 来实现。
我们可以参考一下这个例子,来自于 http://twitter.github.io/scala_school/basics.html#trait
Car = type('Car', (object,), {'brand': ''})
Shiny = type('Shiny', (object,), {'refraction': 0})
BMW = type('BMW', (Car, Shiny,), {'brand': 'BMW', 'refraction': 100})
my_bmw = BMW()
print my_bmw.brand, my_bmw.refraction
你也可以通过以下方式将构造函数传递给 BMW
def bmw_init(self, refraction):
self.refraction = refraction
BMW = type('BMW', (Car, Shiny,), {'brand': 'BMW', '__init__': bmw_init})
c1, c2 = BMW(10), BMW(100)
print c1.refraction, c2.refraction
Scala中的特性(traits)最接近的解决方案是抽象基类(Abstract Base Classes)。这些抽象基类可以在abc模块中找到:
import abc
class Transformation(DB):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def transform(self, obj):
pass
def find(self, id):
return self.transform(super(Transformation, self).get(id))
然后你需要通过正确实现抽象方法来继承Transformation类。
顺便说一下,你也可以通过在你想要作为抽象的方法上抛出NotImplementedError
来模拟abc的功能。ABCMeta只是让你无法创建抽象类的实例。
另外,Python 3中的元类和super
的语法会有一些不同(而且更好!)。
最简单的解决办法可能就是再创建一个子类。
# assuming sensible bases:
class DB(object):
...
class Transformation(object):
def transform(self, obj):
...
def get(self, id):
return self.transform(super(Transformation, self).get(id))
class Cache(object):
def __init__(self, *args, **kwargs):
self.cache = Cache()
super(Cache, self).__init__(*args, **kwargs)
def get(self, id):
if id in self.cache:
return self.cache.get(id)
else:
self.cache.set(id, super(Cache, self).get(id))
return self.cache.get(id)
class DBwithTransformation(Transformation, DB):
# empty body
pass
如果你执意不想给这个类起个名字,你可以直接调用 type
。替换掉
class DBwithTransformation(Transformation, DB):
pass
db = DBwithTransformation(arg1, arg2, ...)
为
db = type("DB", (Transformation, DB), {})(arg1, arg2, ...)
这其实和Scala的例子差不多。
由于Python类型系统的一个细微之处,混合类(mixins)在基类列表中会排在主类(DB
)之前。如果不这样做,混合类就无法正确覆盖主基类的方法。
这个细微之处还可以让你把额外的功能做成真正的派生类。菱形继承模式在这里不是问题;基类只会出现一次,无论有多少个中间基类从它们继承(毕竟,它们最终都是从 object
继承的)。