Возможно ли подражать чертам Scala в Python? - PullRequest
6 голосов
/ 05 июня 2011

Я хочу создать легкие интерфейсы с методами, которые я могу подключить к классам. Вот короткий пример в 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 или обоих. Обратите внимание, что 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? Я знаю, что существует пакет Traits для Python, но его назначение кажется другим.

Ответы [ 4 ]

10 голосов
/ 05 июня 2011

Самое простое решение - это просто создать другой подкласс.

# 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 не смогут корректно переопределить методы основного базового класса.

Эта та же тонкость может позволить вам иметь дополнительные функции в качестве правильных производных классов. Шаблон наследования алмазов не является проблемой; базовые классы появляются только один раз, независимо от того, сколько промежуточных базовых классов наследуют от них (в конце концов, все они в конечном счете наследуются от object).

4 голосов
/ 05 июня 2011

Наиболее близким решением для черт 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 с надлежащей реализацией для абстрактных методов.

Кстати, вы также можете имитировать abc, просто подняв NotImplementedErrorна методы, которые вы хотите, как абстрактные.ABCMeta просто не позволяет создавать экземпляры абстрактных классов.

PS: синтаксис Python 3 для обоих метаклассов и super будет немного отличаться (и лучше!).

0 голосов
/ 11 октября 2013

Это альтернатива ответу с использованием класса. Если вы хотите создавать черты во время выполнения, давайте злоупотребим 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
0 голосов
/ 05 июня 2011

Взгляните на проект Python Traits в Enthought.Но то, что вы описываете, похоже на обычные свойства Python.

...