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.
Traits are used to share interfaces and fields between classes. They are similar to Java 8's interfaces. Classes and objects can extend traits, but traits cannot be instantiated and therefore have no parameters.
¶ A trait is a type definition that can be used for normal Python object attributes, giving the attributes some additional characteristics: Initialization: A trait has a default value, which is automatically set as the initial value of an attribute, before its first use in a program.
Objects and materials have different characteristics or properties. The properties of materials include features such as color, size, and shape; whether theyare rough or smooth, shiny or dull, hard or soft, and flexible or stiff.
In scala, trait mixins means you can extend any number of traits with a class or abstract class. You can extend only traits or combination of traits and class or traits and abstract class. It is necessary to maintain order of mixins otherwise compiler throws an error.
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
).
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!).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With