Recentemente ho scoperto (o meglio ho capito come usare) l'ereditarietà multipla di Python, e temo che ora lo sto usando nei casi in cui non è una buona idea. Voglio avere una sorgente di dati di partenza ( NewsCacheDB
, TwitterStream
) che viene trasformata in vari modi ( Vectorize
, SelectKBest
, SelectPercentile
).
Mi sono ritrovato a scrivere il seguente tipo di codice ( Esempio 1 ) (il codice effettivo è un po 'più complesso ma l'idea è la stessa). Il punto è che per ExperimentA
e ExperimentB
posso definire esattamente che cosa self.data
è, facendo semplicemente affidamento sull'ereditarietà delle classi. È davvero un modo utile per ottenere il comportamento desiderato?
Potrei anche usare decoratori ( Esempio 2 ). Usare i decoratori sarebbe meno codice.
Quale approccio è preferibile? Non sto cercando argomenti del tipo "Mi piace scrivere decoratori migliori", ma piuttosto argomenti su
- leggibilità
- manutenibilità
- testability
- pitonicità (sì, è una parola)
ESEMPIO 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
ESEMPIO 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