10

I want to create lightweight interfaces with methods that I can plug into classes. Here is an short example in 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)
    }
  }   
}

With these classes (traits) we can instantiate DB classes with Transformation, with Cache, or both. Note that Transformation has an abstract method transform, which still needs to implemented in concrete classes.

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()
}

Is there any way to achieve something like this in Python? I know there exists a Traits package for Python, but its purpose seems to be different.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
rafalotufo
  • 3,862
  • 4
  • 25
  • 28
  • I know there was a proposal for "class decorators". You could implement it yourself if you don't care about special syntax. – Ben Jackson Jun 05 '11 at 00:03

4 Answers4

12

The simplest solution is probably to just make another subclass.

# 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

If you stubbornly refuse to actually give the class a name, you can call type directly. replace

class DBwithTransformation(Transformation, DB):
    pass

db = DBwithTransformation(arg1, arg2, ...)

with

db = type("DB", (Transformation, DB), {})(arg1, arg2, ...)

Which isn't too much worse than the Scala example.

Due to a subtlety of the python type system, the mixins, which do not inherit from the primary class (DB) appear first in the bases list. Not doing so will prevent the mixin classes from properly overriding the methods of the primary base class.

That same subtlety can allow you to have the extra features be proper derived classes. The diamond inheritance pattern is a non-issue; base classes only appear once, no matter how many intermediate base classes inherit from them (after all, they all ultimately inherit from object).

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • 1
    You can make a superclass for `DB` with `@classmethod def with(cls, *args): return type(cls.__name__, args + (cls,), {})` to get the syntax `DB.with(Transformation, Cache)(arg1, ...)`. Except don't use `with` because it's a keyword in 2.6. – Ben Jackson Jun 05 '11 at 00:19
  • @TokenMacGuy: Seems simple. But how do we refer to the super classe's get() method in each of the mixins? – rafalotufo Jun 05 '11 at 00:19
  • @Ben Jackson: that's perverse enough to make me want to write it down! – SingleNegationElimination Jun 05 '11 at 00:21
  • @rafalotufo: The same way you do in any new-style python object, with `super(Class, self).get()` – Ben Jackson Jun 05 '11 at 00:26
  • Edited my answer to show this. `super` calls the next base class of the *instance* not the next base class of the class that defines the method. This is the *quirk* of python's type system I was talking about. Once the class has been defined, the branching, hierarchical nature is lost, and the bases are *linearized* – SingleNegationElimination Jun 05 '11 at 00:28
  • I've changed the example so that transform method is abstract. What would be the best way to give it an implementation? `class DBwithTransformation(Transformation, DB): def transform(self, obj): return obj.lower()` – rafalotufo Jun 05 '11 at 01:10
  • @rafalotufo: JBernardo explains abstract classes, have a look at his answer. Another option is to simply not specify the transform method at all in the Transformation class. – SingleNegationElimination Jun 05 '11 at 05:00
5

The closest solution to Scala traits are Abstract Base Classes. They are available in the abc module:

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))

then you must subclass the Transformation class with proper implementation to abstract methods.

BTW, you can also simulate abc's by just raising NotImplementedError on methods you want as abstract. ABCMeta just don't let you create instances of abstract classes.

PS: Python 3 syntax to both metaclasses and super will be a little bit different (and better!).

JBernardo
  • 32,262
  • 10
  • 90
  • 115
0

This is an alternative to the answer using Class. If you want to make Traits during runtime, let's abuse type().

Take an example from 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

You can pass constructor into BMW as well by

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
mrkschan
  • 689
  • 1
  • 7
  • 14
-5

Take a look at the Python Traits project at Enthought. But what you are describing seems to be the same as regular Python properties.

Keith
  • 42,110
  • 11
  • 57
  • 76
  • 1
    Does not appear to be related to the question. – Ben Jackson Jun 05 '11 at 00:05
  • 1
    Same word "trait" but different idea. His example use of `get` adds extra confusion. Scala traits are not about "properties". – Ben Jackson Jun 05 '11 at 00:12
  • @ben yes, so I edited my question since the use of get and set looks a lot like descriptor protocol in Python, which is used to implement properties. – Keith Jun 05 '11 at 00:20