I recently discovered (or rather realised how to use) Python's multiple inheritance, and am afraid I'm now using it in cases where it's not a good fit. I want to have some starting data source (NewsCacheDB
,TwitterStream
) that gets transformed in various ways (Vectorize
,SelectKBest
,SelectPercentile
).
I found myself writing the following sort of code (Example 1) (the actual code is a bit more complex but the idea is the same). The point being that for ExperimentA
and ExperimentB
I can define exactly what self.data
is, by just relying on class inheritance. Is this really a useful way of achieving the desired behaviour?
I could also use decorators (Example 2). Using the decorators would be less code.
Which approach is preferable? I'm not looking for arguments of the "I like writing decorators better" kind, but rather arguments about
- readability
- maintainability
- testability
- pythonicity (yes it's a word).
EXAMPLE 1
class NewsCacheDB(object):
"""Play back cached news articles from a database"""
def __init__(self):
super(NewsArticleCache, self).__init__()
@property
def data(self):
# setup access to data base
while db.isalive():
yield db.next() # slight simplification here
class TwitterCacheDB(object):
"""Play back cached tweets from a database"""
def __init__(self):
super(TwitterCache, self).__init__()
@property
def data(self):
# setup access to data base
while db.isalive():
yield db.next() # slight simplification here
class TwitterStream(object):
def __init__(self):
super(TwitterStream, self).__init__()
@property
def data(self):
# setup access to live twitter stream
while stream.isalive():
yield stream.next()
class Vectorize(object):
"""Turn raw data into numpy vectors"""
def __init__(self):
super(Vectorize, self).__init__()
@property
def data(self):
for item in super(Vectorize, self).data:
transformed = vectorize(item) # slight simplification here
yield transformed
class SelectKBest(object):
"""Select K best features based on some metric"""
def __init__(self):
super(SelectKBest, self).__init__()
@property
def data(self):
for item in super(SelectKBest, self).data:
transformed = select_kbest(item) # slight simplification here
yield transformed
class SelectPercentile(object):
"""Select the top X percentile features based on some metric"""
def __init__(self):
super(SelectPercentile, self).__init__()
@property
def data(self):
for item in super(SelectPercentile, self).data:
transformed = select_kbest(item) # slight simplification here
yield transformed
class ExperimentA(SelectKBest, Vectorize, TwitterCacheDB):
# lots of control code goes here
class ExperimentB(SelectKBest, Vectorize, NewsCacheDB):
# lots of control code goes here
class ExperimentC(SelectPercentile, Vectorize, NewsCacheDB):
# lots of control code goes here
EXAMPLE 2
def multiply(fn):
def wrapped(self):
return fn(self) * 2
return wrapped
def twitter_cacheDB(fn):
def wrapped(self):
user, pass = fn(self)
# setup access to data base
while db.isalive():
yield db.next() # slight simplification here
return wrapped
def twitter_live(fn):
def wrapped(self):
user, pass = fn(self)
# setup access to data base
while stream.isalive():
yield stream.next() # slight simplification here
return wrapped
def news_cacheDB(fn):
def wrapped(self):
user, pass = fn(self)
# setup access to data base
while db.isalive():
yield db.next() # slight simplification here
return wrapped
def vectorize(fn):
def wrapped(self):
for item in fn():
transformed = do_vectorize(item) # slight simplification here
yield transformed
yield wrapped
def select_kbest(fn):
def wrapped(self):
for item in fn():
transformed = do_selection(item) # slight simplification here
yield transformed
yield wrapped
class ExperimentA():
@property
@select_kbest
@vectorize
@twitter_cacheDB
def a(self):
return 'me','123' # return user and pass to connect to DB
class ExperimentB():
@property
@select_kbest
@vectorize
@news_cacheDB
def a(self):
return 'me','123' # return user and pass to connect to DB
value_A
andvalue_B
will load data formatted differently from different sources and transform it to a standard representation used inside the software. The other decorators then work on that representation doing vectorisation, feature selection etc. It isn't about the parameters to the vectorizer but the vectorizer itself.value
methods that you are decorating instead of decoratingpass
ed methods withvalue
. I just question any design that necessitates the creation of stub methods for a functional purpose.