rafalotufo rafalotufo - 5 months ago 16
Python Question

Is it possible to emulate Scala's traits in Python?

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.

Answer Source

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