在Python中能模拟Scala的特质吗?

10 投票
4 回答
10898 浏览
提问于 2025-04-16 18:58

我想创建一些轻量级的接口,这些接口里有一些方法,可以插入到类里面。下面是一个用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 个回答

0

这是一个使用类的替代方案。如果你想在运行时创建特性(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
5

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的语法会有一些不同(而且更好!)。

12

最简单的解决办法可能就是再创建一个子类。

# 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 继承的)。

撰写回答