Возможно ли подражать чертам Scala в Python?

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

Решение

Самым простым решением, вероятно, является просто создание другого подкласса.

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

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

Наиболее близким решением для черт 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 будет немного по-другому (и лучше!).

Это альтернатива ответу с использованием класса. Если вы хотите создавать черты во время выполнения, давайте злоупотребим type().

Возьмите пример с http://twitter.github.io/scala_school/basics.html

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

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

Другие вопросы по тегам