【问题标题】:Is it possible to emulate Scala's traits in Python?是否可以在 Python 中模拟 Scala 的特性?
【发布时间】:2011-09-08 13:36:50
【问题描述】:

我想使用可以插入类的方法创建轻量级接口。这是 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)
    }
  }   
}

通过这些类(特征),我们可以使用 Transformation、Cache 或两者来实例化 DB 类。注意,Transformation 有一个抽象方法 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 包,但它的用途似乎不同。

【问题讨论】:

  • 我知道有一个关于“类装饰器”的提议。如果您不关心特殊语法,您可以自己实现它。

标签: python scala language-comparisons


【解决方案1】:

最简单的解决方案可能是创建另一个子类。

# 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 类型系统的微妙之处,不从主类 (DB) 继承的 mixin 会首先出现在基类列表中。不这样做会阻止 mixin 类正确覆盖主要基类的方法。

同样的微妙之处可以让您将额外的功能作为适当的派生类。菱形继承模式不是问题;基类只出现一次,无论有多少中间基类继承自它们(毕竟它们最终都继承自object)。

【讨论】:

  • 您可以使用@classmethod def with(cls, *args): return type(cls.__name__, args + (cls,), {})DB 创建一个超类以获取语法DB.with(Transformation, Cache)(arg1, ...)。除了不要使用with,因为它是 2.6 中的关键字。
  • @TokenMacGuy:看起来很简单。但是我们如何在每个 mixin 中引用超类的 get() 方法呢?
  • @Ben Jackson:这太反常了,让我想写下来!
  • @rafalotufo:就像你在任何新型 python 对象中所做的一样,super(Class, self).get()
  • 编辑了我的答案以显示这一点。 super 调用 instance 的下一个基类,而不是定义该方法的类的下一个基类。这就是我所说的python 类型系统的怪癖。一旦定义了类,分支、层次结构的性质就消失了,基础被线性化
【解决方案2】:

最接近 Scala 特征的解决方案是抽象基类。 它们在 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 只是不允许您创建抽象类的实例。

PS:元类和super 的 Python 3 语法会有点不同(而且更好!)。

【讨论】:

    【解决方案3】:

    这是使用 Class 的答案的替代方案。如果你想在运行时创建 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
    

    【讨论】:

      【解决方案4】:

      看看 Enthought 上的 Python Traits project。但是您所描述的似乎与常规 Python 属性相同。

      【讨论】:

      • 似乎与问题无关。
      • 同一个词“特质”但不同的想法。他对get 的示例使用增加了额外的混乱。 Scala 特征与“属性”无关。
      • @ben 是的,所以我编辑了我的问题,因为 get 和 set 的使用看起来很像 Python 中用于实现属性的描述符协议。
      猜你喜欢
      • 2018-08-09
      • 2012-02-13
      • 1970-01-01
      • 2011-09-21
      • 1970-01-01
      • 2011-08-12
      • 1970-01-01
      • 1970-01-01
      • 2017-12-08
      相关资源
      最近更新 更多