Sharing attributes between classes: is multi-inheritance right and “pythonic” here?What is the difference between old style and new style classes in Python?What is the difference between Python's list methods append and extend?Difference between abstract class and interface in PythonHow to know if an object has an attribute in PythonPython class inherits objectPython private class variables that aren't class variablesPythonic way to create a long multi-line stringPython Error Init of ClassDerived panel classes in wxpythonpython decorator for class methods
How was Luke's prosthetic hand in Episode V filmed?
How to change word under cursor to upper case in command mode with shortcut?
Inscriptio Labyrinthica
Making a Dataset that emulates `ls -tlra`?
What is a Romeo Word™?
Why would word of Princess Leia's capture generate sympathy for the Rebellion in the Senate?
Is it possible to have a career in SciComp without contributing to arms research?
What image should I install on VirtualBox for practising dev ops
When a ball on a rope swings in a circle, is there both centripetal force and tension force?
Locked-up DOS computer beeped on keypress. What mechanism caused that?
Why is Google approaching my VPS machine?
Transistor power dissipation rating
Wait or be waiting?
How did Jayne know when to shoot?
Could a US citizen born through "birth tourism" become President?
Why are flying carpets banned while flying brooms are not?
How should I interpret a promising preprint that was never published in a peer-reviewed journal?
Do pedestrians imitate auto traffic?
Flashing the ESP8266 12F from raspberry
extract the top-level domain and the second-level domain from a URL
Draw parts of a split rectangle in a dashed/dotted style
How slow ( not zero) can a car engine run without hurting engine and saving on fuel
What does <recently read> etc. mean in TeX's error message
A spacecraft is travelling at X units per hour. But relative to what exactly? Does it depend on orbit? How?
Sharing attributes between classes: is multi-inheritance right and “pythonic” here?
What is the difference between old style and new style classes in Python?What is the difference between Python's list methods append and extend?Difference between abstract class and interface in PythonHow to know if an object has an attribute in PythonPython class inherits objectPython private class variables that aren't class variablesPythonic way to create a long multi-line stringPython Error Init of ClassDerived panel classes in wxpythonpython decorator for class methods
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
I have an use case where multi-inheritance seems the right way to go. But it implies sharing attributes between "sibling" classes, attributes that are initialized on other classes (so somehow unknown for them).
I'm asking if this below is a "right" and "pythonic" model, or should I better go with a dertivated-classes model.
Let's say we want to develop different deliverers, which will take some source data, apply some format to it, and send it through some channel. And this three parts (data - format - send) can be customizable for each case.
First, come code to make the examples below working:
import sys
PY3 = not sys.version_info < (3,)
from string import Template
import csv, io, smtplib, requests, os
def read_test_movies(year_from, year_to, genre= None):
TEST_MOVIES= [
'year': 1971, 'release': '01/01/1971', 'genre': 'thriller', 'title': 'Play Misty for Me',
'year': 1973, 'release': '02/02/1973', 'genre': 'romantic', 'title': 'Breezy',
'year': 1976, 'release': '03/03/1976', 'genre': 'western', 'title': 'The Outlaw',
'year': 1986, 'release': '04/04/1986', 'genre': 'war', 'title': 'Heartbreak',
'year': 1988, 'release': '05/05/1988', 'genre': 'music', 'title': 'Bird',
'year': 1992, 'release': '06/06/1992', 'genre': 'western', 'title': 'Unforgiven',
'year': 1995, 'release': '07/07/1995', 'genre': 'romantic', 'title': 'The Bridges of Madison County',
'year': 2000, 'release': '08/08/2000', 'genre': 'space', 'title': 'Space Cowboys',
'year': 2003, 'release': '09/09/2003', 'genre': 'trhiller', 'title': 'Mystic River',
'year': 2004, 'release': '10/10/2004', 'genre': 'sports', 'title': 'Million Dollar Baby',
'year': 2006, 'release': '11/11/2006', 'genre': 'war', 'title': 'Flags of Our Fathers',
'year': 2006, 'release': '12/12/2006', 'genre': 'war', 'title': 'Letters from Iwo Jima',
'year': 2008, 'release': '13/11/2008', 'genre': 'drama', 'title': 'Changeling',
'year': 2008, 'release': '14/10/2008', 'genre': 'drama', 'title': 'Gran Torino',
'year': 2009, 'release': '15/09/2009', 'genre': 'sports', 'title': 'Invictus',
'year': 2010, 'release': '16/08/2010', 'genre': 'drama', 'title': 'Hereafter',
'year': 2011, 'release': '17/07/2011', 'genre': 'drama', 'title': 'J. Edgar',
'year': 2014, 'release': '18/06/2014', 'genre': 'war', 'title': 'American Sniper',
'year': 2016, 'release': '19/05/2016', 'genre': 'drama', 'title': 'Sully'
]
out= []
for m in TEST_MOVIES:
if year_from <= m['year'] and m['year'] <= year_to:
if genre is None or (genre is not None and genre == m['genre']):
out.append(m)
return out
Being this three parts (data - format - send) so distinguishable, we would start with these interface-like classes (I guess abc could be used too):
class ITheData(object):
def __init__(self, year_from, year_to, genre= None):
self.year_from= year_from
self.year_to = year_to
self.genre = genre
def readMovies(self):
raise NotImplementedError('%s.readMovies() must be implemented' % self.__class__.__name__)
class ITheFormat(object):
def filename(self):
raise NotImplementedError('%s.filename() must be implemented' % self.__class__.__name__)
def make(self):
raise NotImplementedError('%s.make() must be implemented' % self.__class__.__name__)
class ITheSend(object):
def send(self):
raise NotImplementedError('%s.send() must be implemented' % self.__class__.__name__)
For each custom deliver, we will subclass the three of them, and put them together in a class like:
class ITheDeliverer(ITheData, ITheFormat, ITheSend):
def deliver(self):
raise NotImplementedError('%s.deliver() must be implemented' % self.__class__.__name__)
So, we could have two different data sources. Apart from source, they may differ on post-processing actions. Although for simplicity I'm just doing a self.readMovies() all over the place, it could be some other custom method on the subclass.
class TheIMDBData(ITheData):
def readMovies(self):
# movies = some_read_from_IMDB(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
class TheTMDbData(ITheData):
def readMovies(self):
# movies = some_read_from_TMDb(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
We could use also two different formats:
class TheTXTFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.txt').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
for movie in self.readMovies():
line= Template('$title, released on $release').substitute(**movie)
line+= 'n'
strio.write(line)
strio.seek(0)
return strio.read()
class TheCSVFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.csv').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
writer = csv.writer(strio, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
header = ('Title', 'Release')
writer.writerow(header)
for movie in self.readMovies():
writer.writerow((movie['title'], movie['release']))
strio.seek(0)
return strio.read()
And two different sending channels:
class TheMailSend(ITheSend):
host = 'localhost'
sender = 'movie@spammer.com'
receivers = ['movie@spammed.com']
def send(self):
# Here `make()` is unknown
print('TheMailSend.send() Sending to %s' % str(self.receivers))
try:
message = self.make() # Format agnostic
smtpObj = smtplib.SMTP(self.host)
smtpObj.sendmail(self.sender, self.receivers, message)
return True, 'ok'
except Exception as ss:
return False, str(ss)
class TheWSSend(ITheSend):
url = 'spammed.com/movies/send'
def send(self):
# Here `make()` is unknown
print('TheWSSend.send() Sending to %s' % str(self.url))
try:
content = self.make() # Format agnostic
s= requests.Session()
response= s.post(url= self.url, data= 'content': content)
s.close()
if response.status_code == 200:
return True, 'ok'
else:
return False, response.status_code
except Exception as ss:
return False, str(ss)
So, we could end with some deliverers like these:
class TheIMDBToTXTFile(ITheDeliverer, TheIMDBData, TheTXTFormat):
def __init__(self, year_from, year_to, genre= None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
filepath= os.path.join('/tmp', self.filename())
f= open(filepath, 'w')
f.write(self.make())
f.close()
print('TheIMDBToTXTFile.deliver() => Successfully delivered to %s' % str(filepath))
class TheIMDBToWS(ITheDeliverer, TheIMDBData, TheTXTFormat, TheWSSend):
def __init__(self, year_from, year_to, genre=None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg = self.send()
if ok:
print('TheIMDBToWS.deliver() => Successfully delivered!')
else:
print('TheIMDBToWS.deliver() => Error delivering: %s' % str(msg))
class TheTMDbToMail(ITheDeliverer, TheTMDbData, TheCSVFormat, TheMailSend):
def __init__(self, year_from, year_to, genre=None):
TheTMDbData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg= self.send()
if ok:
print('TheTMDbToMail.deliver() => Successfully delivered!')
else:
print('TheTMDbToMail.deliver() => Error delivering: %s' % str(msg))
And they work fine -with obvious connection errors-:
>>> imdbToTxt = TheIMDBToTXTFile(year_from= 2000, year_to= 2010)
>>> imdbToTxt.deliver()
TheIMDBToTXTFile.deliver() => Successfully delivered to /tmp/movies_of_None_from_200_to_2010.txt
>>>
>>> imdbToWs = TheIMDBToWS(year_from= 2000, year_to= 2010)
>>> imdbToWs.deliver()
TheWSSend.send() Sending to http://spammed.com/movies/send?
TheIMDBToWS.deliver() => Error delivering: 405
>>>
>>> tmdbToMail = TheTMDbToMail(year_from= 1980, year_to= 2019, genre= 'war')
>>> tmdbToMail.deliver()
TheMailSend.send() Sending to ['movie@spammed.com']
TheTMDbToMail.deliver() => Error delivering: [Errno 111] Connection refused
But, as commented, some attributes are unknown for some classes, and the linter is -obviously- complaining about it:
Instance of 'TheTXTFormat' has no 'genre' member
Instance of 'TheTXTFormat' has no 'year_from' member
Instance of 'TheTXTFormat' has no 'year_to' member
Instance of 'TheTXTFormat' has no 'readMovies' member
Instance of 'TheCSVFormat' has no 'genre' member
Instance of 'TheCSVFormat' has no 'year_from' member
Instance of 'TheCSVFormat' has no 'year_to' member
Instance of 'TheCSVFormat' has no 'readMovies' member
Instance of 'TheMailSend' has no 'make' member
Instance of 'TheWSSend' has no 'make' member
So, the question remains: is here multi-inheritance a good model?
The alternatives could be: a derivated-classes model, or just independent classes and passing around parameters like data or formatter. But none of them seem so simple as multi-inheritance (although they'd fix linter -and probably conceptual- problems).
python python-3.x python-2.7 multiple-inheritance
add a comment |
I have an use case where multi-inheritance seems the right way to go. But it implies sharing attributes between "sibling" classes, attributes that are initialized on other classes (so somehow unknown for them).
I'm asking if this below is a "right" and "pythonic" model, or should I better go with a dertivated-classes model.
Let's say we want to develop different deliverers, which will take some source data, apply some format to it, and send it through some channel. And this three parts (data - format - send) can be customizable for each case.
First, come code to make the examples below working:
import sys
PY3 = not sys.version_info < (3,)
from string import Template
import csv, io, smtplib, requests, os
def read_test_movies(year_from, year_to, genre= None):
TEST_MOVIES= [
'year': 1971, 'release': '01/01/1971', 'genre': 'thriller', 'title': 'Play Misty for Me',
'year': 1973, 'release': '02/02/1973', 'genre': 'romantic', 'title': 'Breezy',
'year': 1976, 'release': '03/03/1976', 'genre': 'western', 'title': 'The Outlaw',
'year': 1986, 'release': '04/04/1986', 'genre': 'war', 'title': 'Heartbreak',
'year': 1988, 'release': '05/05/1988', 'genre': 'music', 'title': 'Bird',
'year': 1992, 'release': '06/06/1992', 'genre': 'western', 'title': 'Unforgiven',
'year': 1995, 'release': '07/07/1995', 'genre': 'romantic', 'title': 'The Bridges of Madison County',
'year': 2000, 'release': '08/08/2000', 'genre': 'space', 'title': 'Space Cowboys',
'year': 2003, 'release': '09/09/2003', 'genre': 'trhiller', 'title': 'Mystic River',
'year': 2004, 'release': '10/10/2004', 'genre': 'sports', 'title': 'Million Dollar Baby',
'year': 2006, 'release': '11/11/2006', 'genre': 'war', 'title': 'Flags of Our Fathers',
'year': 2006, 'release': '12/12/2006', 'genre': 'war', 'title': 'Letters from Iwo Jima',
'year': 2008, 'release': '13/11/2008', 'genre': 'drama', 'title': 'Changeling',
'year': 2008, 'release': '14/10/2008', 'genre': 'drama', 'title': 'Gran Torino',
'year': 2009, 'release': '15/09/2009', 'genre': 'sports', 'title': 'Invictus',
'year': 2010, 'release': '16/08/2010', 'genre': 'drama', 'title': 'Hereafter',
'year': 2011, 'release': '17/07/2011', 'genre': 'drama', 'title': 'J. Edgar',
'year': 2014, 'release': '18/06/2014', 'genre': 'war', 'title': 'American Sniper',
'year': 2016, 'release': '19/05/2016', 'genre': 'drama', 'title': 'Sully'
]
out= []
for m in TEST_MOVIES:
if year_from <= m['year'] and m['year'] <= year_to:
if genre is None or (genre is not None and genre == m['genre']):
out.append(m)
return out
Being this three parts (data - format - send) so distinguishable, we would start with these interface-like classes (I guess abc could be used too):
class ITheData(object):
def __init__(self, year_from, year_to, genre= None):
self.year_from= year_from
self.year_to = year_to
self.genre = genre
def readMovies(self):
raise NotImplementedError('%s.readMovies() must be implemented' % self.__class__.__name__)
class ITheFormat(object):
def filename(self):
raise NotImplementedError('%s.filename() must be implemented' % self.__class__.__name__)
def make(self):
raise NotImplementedError('%s.make() must be implemented' % self.__class__.__name__)
class ITheSend(object):
def send(self):
raise NotImplementedError('%s.send() must be implemented' % self.__class__.__name__)
For each custom deliver, we will subclass the three of them, and put them together in a class like:
class ITheDeliverer(ITheData, ITheFormat, ITheSend):
def deliver(self):
raise NotImplementedError('%s.deliver() must be implemented' % self.__class__.__name__)
So, we could have two different data sources. Apart from source, they may differ on post-processing actions. Although for simplicity I'm just doing a self.readMovies() all over the place, it could be some other custom method on the subclass.
class TheIMDBData(ITheData):
def readMovies(self):
# movies = some_read_from_IMDB(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
class TheTMDbData(ITheData):
def readMovies(self):
# movies = some_read_from_TMDb(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
We could use also two different formats:
class TheTXTFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.txt').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
for movie in self.readMovies():
line= Template('$title, released on $release').substitute(**movie)
line+= 'n'
strio.write(line)
strio.seek(0)
return strio.read()
class TheCSVFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.csv').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
writer = csv.writer(strio, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
header = ('Title', 'Release')
writer.writerow(header)
for movie in self.readMovies():
writer.writerow((movie['title'], movie['release']))
strio.seek(0)
return strio.read()
And two different sending channels:
class TheMailSend(ITheSend):
host = 'localhost'
sender = 'movie@spammer.com'
receivers = ['movie@spammed.com']
def send(self):
# Here `make()` is unknown
print('TheMailSend.send() Sending to %s' % str(self.receivers))
try:
message = self.make() # Format agnostic
smtpObj = smtplib.SMTP(self.host)
smtpObj.sendmail(self.sender, self.receivers, message)
return True, 'ok'
except Exception as ss:
return False, str(ss)
class TheWSSend(ITheSend):
url = 'spammed.com/movies/send'
def send(self):
# Here `make()` is unknown
print('TheWSSend.send() Sending to %s' % str(self.url))
try:
content = self.make() # Format agnostic
s= requests.Session()
response= s.post(url= self.url, data= 'content': content)
s.close()
if response.status_code == 200:
return True, 'ok'
else:
return False, response.status_code
except Exception as ss:
return False, str(ss)
So, we could end with some deliverers like these:
class TheIMDBToTXTFile(ITheDeliverer, TheIMDBData, TheTXTFormat):
def __init__(self, year_from, year_to, genre= None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
filepath= os.path.join('/tmp', self.filename())
f= open(filepath, 'w')
f.write(self.make())
f.close()
print('TheIMDBToTXTFile.deliver() => Successfully delivered to %s' % str(filepath))
class TheIMDBToWS(ITheDeliverer, TheIMDBData, TheTXTFormat, TheWSSend):
def __init__(self, year_from, year_to, genre=None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg = self.send()
if ok:
print('TheIMDBToWS.deliver() => Successfully delivered!')
else:
print('TheIMDBToWS.deliver() => Error delivering: %s' % str(msg))
class TheTMDbToMail(ITheDeliverer, TheTMDbData, TheCSVFormat, TheMailSend):
def __init__(self, year_from, year_to, genre=None):
TheTMDbData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg= self.send()
if ok:
print('TheTMDbToMail.deliver() => Successfully delivered!')
else:
print('TheTMDbToMail.deliver() => Error delivering: %s' % str(msg))
And they work fine -with obvious connection errors-:
>>> imdbToTxt = TheIMDBToTXTFile(year_from= 2000, year_to= 2010)
>>> imdbToTxt.deliver()
TheIMDBToTXTFile.deliver() => Successfully delivered to /tmp/movies_of_None_from_200_to_2010.txt
>>>
>>> imdbToWs = TheIMDBToWS(year_from= 2000, year_to= 2010)
>>> imdbToWs.deliver()
TheWSSend.send() Sending to http://spammed.com/movies/send?
TheIMDBToWS.deliver() => Error delivering: 405
>>>
>>> tmdbToMail = TheTMDbToMail(year_from= 1980, year_to= 2019, genre= 'war')
>>> tmdbToMail.deliver()
TheMailSend.send() Sending to ['movie@spammed.com']
TheTMDbToMail.deliver() => Error delivering: [Errno 111] Connection refused
But, as commented, some attributes are unknown for some classes, and the linter is -obviously- complaining about it:
Instance of 'TheTXTFormat' has no 'genre' member
Instance of 'TheTXTFormat' has no 'year_from' member
Instance of 'TheTXTFormat' has no 'year_to' member
Instance of 'TheTXTFormat' has no 'readMovies' member
Instance of 'TheCSVFormat' has no 'genre' member
Instance of 'TheCSVFormat' has no 'year_from' member
Instance of 'TheCSVFormat' has no 'year_to' member
Instance of 'TheCSVFormat' has no 'readMovies' member
Instance of 'TheMailSend' has no 'make' member
Instance of 'TheWSSend' has no 'make' member
So, the question remains: is here multi-inheritance a good model?
The alternatives could be: a derivated-classes model, or just independent classes and passing around parameters like data or formatter. But none of them seem so simple as multi-inheritance (although they'd fix linter -and probably conceptual- problems).
python python-3.x python-2.7 multiple-inheritance
add a comment |
I have an use case where multi-inheritance seems the right way to go. But it implies sharing attributes between "sibling" classes, attributes that are initialized on other classes (so somehow unknown for them).
I'm asking if this below is a "right" and "pythonic" model, or should I better go with a dertivated-classes model.
Let's say we want to develop different deliverers, which will take some source data, apply some format to it, and send it through some channel. And this three parts (data - format - send) can be customizable for each case.
First, come code to make the examples below working:
import sys
PY3 = not sys.version_info < (3,)
from string import Template
import csv, io, smtplib, requests, os
def read_test_movies(year_from, year_to, genre= None):
TEST_MOVIES= [
'year': 1971, 'release': '01/01/1971', 'genre': 'thriller', 'title': 'Play Misty for Me',
'year': 1973, 'release': '02/02/1973', 'genre': 'romantic', 'title': 'Breezy',
'year': 1976, 'release': '03/03/1976', 'genre': 'western', 'title': 'The Outlaw',
'year': 1986, 'release': '04/04/1986', 'genre': 'war', 'title': 'Heartbreak',
'year': 1988, 'release': '05/05/1988', 'genre': 'music', 'title': 'Bird',
'year': 1992, 'release': '06/06/1992', 'genre': 'western', 'title': 'Unforgiven',
'year': 1995, 'release': '07/07/1995', 'genre': 'romantic', 'title': 'The Bridges of Madison County',
'year': 2000, 'release': '08/08/2000', 'genre': 'space', 'title': 'Space Cowboys',
'year': 2003, 'release': '09/09/2003', 'genre': 'trhiller', 'title': 'Mystic River',
'year': 2004, 'release': '10/10/2004', 'genre': 'sports', 'title': 'Million Dollar Baby',
'year': 2006, 'release': '11/11/2006', 'genre': 'war', 'title': 'Flags of Our Fathers',
'year': 2006, 'release': '12/12/2006', 'genre': 'war', 'title': 'Letters from Iwo Jima',
'year': 2008, 'release': '13/11/2008', 'genre': 'drama', 'title': 'Changeling',
'year': 2008, 'release': '14/10/2008', 'genre': 'drama', 'title': 'Gran Torino',
'year': 2009, 'release': '15/09/2009', 'genre': 'sports', 'title': 'Invictus',
'year': 2010, 'release': '16/08/2010', 'genre': 'drama', 'title': 'Hereafter',
'year': 2011, 'release': '17/07/2011', 'genre': 'drama', 'title': 'J. Edgar',
'year': 2014, 'release': '18/06/2014', 'genre': 'war', 'title': 'American Sniper',
'year': 2016, 'release': '19/05/2016', 'genre': 'drama', 'title': 'Sully'
]
out= []
for m in TEST_MOVIES:
if year_from <= m['year'] and m['year'] <= year_to:
if genre is None or (genre is not None and genre == m['genre']):
out.append(m)
return out
Being this three parts (data - format - send) so distinguishable, we would start with these interface-like classes (I guess abc could be used too):
class ITheData(object):
def __init__(self, year_from, year_to, genre= None):
self.year_from= year_from
self.year_to = year_to
self.genre = genre
def readMovies(self):
raise NotImplementedError('%s.readMovies() must be implemented' % self.__class__.__name__)
class ITheFormat(object):
def filename(self):
raise NotImplementedError('%s.filename() must be implemented' % self.__class__.__name__)
def make(self):
raise NotImplementedError('%s.make() must be implemented' % self.__class__.__name__)
class ITheSend(object):
def send(self):
raise NotImplementedError('%s.send() must be implemented' % self.__class__.__name__)
For each custom deliver, we will subclass the three of them, and put them together in a class like:
class ITheDeliverer(ITheData, ITheFormat, ITheSend):
def deliver(self):
raise NotImplementedError('%s.deliver() must be implemented' % self.__class__.__name__)
So, we could have two different data sources. Apart from source, they may differ on post-processing actions. Although for simplicity I'm just doing a self.readMovies() all over the place, it could be some other custom method on the subclass.
class TheIMDBData(ITheData):
def readMovies(self):
# movies = some_read_from_IMDB(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
class TheTMDbData(ITheData):
def readMovies(self):
# movies = some_read_from_TMDb(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
We could use also two different formats:
class TheTXTFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.txt').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
for movie in self.readMovies():
line= Template('$title, released on $release').substitute(**movie)
line+= 'n'
strio.write(line)
strio.seek(0)
return strio.read()
class TheCSVFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.csv').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
writer = csv.writer(strio, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
header = ('Title', 'Release')
writer.writerow(header)
for movie in self.readMovies():
writer.writerow((movie['title'], movie['release']))
strio.seek(0)
return strio.read()
And two different sending channels:
class TheMailSend(ITheSend):
host = 'localhost'
sender = 'movie@spammer.com'
receivers = ['movie@spammed.com']
def send(self):
# Here `make()` is unknown
print('TheMailSend.send() Sending to %s' % str(self.receivers))
try:
message = self.make() # Format agnostic
smtpObj = smtplib.SMTP(self.host)
smtpObj.sendmail(self.sender, self.receivers, message)
return True, 'ok'
except Exception as ss:
return False, str(ss)
class TheWSSend(ITheSend):
url = 'spammed.com/movies/send'
def send(self):
# Here `make()` is unknown
print('TheWSSend.send() Sending to %s' % str(self.url))
try:
content = self.make() # Format agnostic
s= requests.Session()
response= s.post(url= self.url, data= 'content': content)
s.close()
if response.status_code == 200:
return True, 'ok'
else:
return False, response.status_code
except Exception as ss:
return False, str(ss)
So, we could end with some deliverers like these:
class TheIMDBToTXTFile(ITheDeliverer, TheIMDBData, TheTXTFormat):
def __init__(self, year_from, year_to, genre= None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
filepath= os.path.join('/tmp', self.filename())
f= open(filepath, 'w')
f.write(self.make())
f.close()
print('TheIMDBToTXTFile.deliver() => Successfully delivered to %s' % str(filepath))
class TheIMDBToWS(ITheDeliverer, TheIMDBData, TheTXTFormat, TheWSSend):
def __init__(self, year_from, year_to, genre=None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg = self.send()
if ok:
print('TheIMDBToWS.deliver() => Successfully delivered!')
else:
print('TheIMDBToWS.deliver() => Error delivering: %s' % str(msg))
class TheTMDbToMail(ITheDeliverer, TheTMDbData, TheCSVFormat, TheMailSend):
def __init__(self, year_from, year_to, genre=None):
TheTMDbData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg= self.send()
if ok:
print('TheTMDbToMail.deliver() => Successfully delivered!')
else:
print('TheTMDbToMail.deliver() => Error delivering: %s' % str(msg))
And they work fine -with obvious connection errors-:
>>> imdbToTxt = TheIMDBToTXTFile(year_from= 2000, year_to= 2010)
>>> imdbToTxt.deliver()
TheIMDBToTXTFile.deliver() => Successfully delivered to /tmp/movies_of_None_from_200_to_2010.txt
>>>
>>> imdbToWs = TheIMDBToWS(year_from= 2000, year_to= 2010)
>>> imdbToWs.deliver()
TheWSSend.send() Sending to http://spammed.com/movies/send?
TheIMDBToWS.deliver() => Error delivering: 405
>>>
>>> tmdbToMail = TheTMDbToMail(year_from= 1980, year_to= 2019, genre= 'war')
>>> tmdbToMail.deliver()
TheMailSend.send() Sending to ['movie@spammed.com']
TheTMDbToMail.deliver() => Error delivering: [Errno 111] Connection refused
But, as commented, some attributes are unknown for some classes, and the linter is -obviously- complaining about it:
Instance of 'TheTXTFormat' has no 'genre' member
Instance of 'TheTXTFormat' has no 'year_from' member
Instance of 'TheTXTFormat' has no 'year_to' member
Instance of 'TheTXTFormat' has no 'readMovies' member
Instance of 'TheCSVFormat' has no 'genre' member
Instance of 'TheCSVFormat' has no 'year_from' member
Instance of 'TheCSVFormat' has no 'year_to' member
Instance of 'TheCSVFormat' has no 'readMovies' member
Instance of 'TheMailSend' has no 'make' member
Instance of 'TheWSSend' has no 'make' member
So, the question remains: is here multi-inheritance a good model?
The alternatives could be: a derivated-classes model, or just independent classes and passing around parameters like data or formatter. But none of them seem so simple as multi-inheritance (although they'd fix linter -and probably conceptual- problems).
python python-3.x python-2.7 multiple-inheritance
I have an use case where multi-inheritance seems the right way to go. But it implies sharing attributes between "sibling" classes, attributes that are initialized on other classes (so somehow unknown for them).
I'm asking if this below is a "right" and "pythonic" model, or should I better go with a dertivated-classes model.
Let's say we want to develop different deliverers, which will take some source data, apply some format to it, and send it through some channel. And this three parts (data - format - send) can be customizable for each case.
First, come code to make the examples below working:
import sys
PY3 = not sys.version_info < (3,)
from string import Template
import csv, io, smtplib, requests, os
def read_test_movies(year_from, year_to, genre= None):
TEST_MOVIES= [
'year': 1971, 'release': '01/01/1971', 'genre': 'thriller', 'title': 'Play Misty for Me',
'year': 1973, 'release': '02/02/1973', 'genre': 'romantic', 'title': 'Breezy',
'year': 1976, 'release': '03/03/1976', 'genre': 'western', 'title': 'The Outlaw',
'year': 1986, 'release': '04/04/1986', 'genre': 'war', 'title': 'Heartbreak',
'year': 1988, 'release': '05/05/1988', 'genre': 'music', 'title': 'Bird',
'year': 1992, 'release': '06/06/1992', 'genre': 'western', 'title': 'Unforgiven',
'year': 1995, 'release': '07/07/1995', 'genre': 'romantic', 'title': 'The Bridges of Madison County',
'year': 2000, 'release': '08/08/2000', 'genre': 'space', 'title': 'Space Cowboys',
'year': 2003, 'release': '09/09/2003', 'genre': 'trhiller', 'title': 'Mystic River',
'year': 2004, 'release': '10/10/2004', 'genre': 'sports', 'title': 'Million Dollar Baby',
'year': 2006, 'release': '11/11/2006', 'genre': 'war', 'title': 'Flags of Our Fathers',
'year': 2006, 'release': '12/12/2006', 'genre': 'war', 'title': 'Letters from Iwo Jima',
'year': 2008, 'release': '13/11/2008', 'genre': 'drama', 'title': 'Changeling',
'year': 2008, 'release': '14/10/2008', 'genre': 'drama', 'title': 'Gran Torino',
'year': 2009, 'release': '15/09/2009', 'genre': 'sports', 'title': 'Invictus',
'year': 2010, 'release': '16/08/2010', 'genre': 'drama', 'title': 'Hereafter',
'year': 2011, 'release': '17/07/2011', 'genre': 'drama', 'title': 'J. Edgar',
'year': 2014, 'release': '18/06/2014', 'genre': 'war', 'title': 'American Sniper',
'year': 2016, 'release': '19/05/2016', 'genre': 'drama', 'title': 'Sully'
]
out= []
for m in TEST_MOVIES:
if year_from <= m['year'] and m['year'] <= year_to:
if genre is None or (genre is not None and genre == m['genre']):
out.append(m)
return out
Being this three parts (data - format - send) so distinguishable, we would start with these interface-like classes (I guess abc could be used too):
class ITheData(object):
def __init__(self, year_from, year_to, genre= None):
self.year_from= year_from
self.year_to = year_to
self.genre = genre
def readMovies(self):
raise NotImplementedError('%s.readMovies() must be implemented' % self.__class__.__name__)
class ITheFormat(object):
def filename(self):
raise NotImplementedError('%s.filename() must be implemented' % self.__class__.__name__)
def make(self):
raise NotImplementedError('%s.make() must be implemented' % self.__class__.__name__)
class ITheSend(object):
def send(self):
raise NotImplementedError('%s.send() must be implemented' % self.__class__.__name__)
For each custom deliver, we will subclass the three of them, and put them together in a class like:
class ITheDeliverer(ITheData, ITheFormat, ITheSend):
def deliver(self):
raise NotImplementedError('%s.deliver() must be implemented' % self.__class__.__name__)
So, we could have two different data sources. Apart from source, they may differ on post-processing actions. Although for simplicity I'm just doing a self.readMovies() all over the place, it could be some other custom method on the subclass.
class TheIMDBData(ITheData):
def readMovies(self):
# movies = some_read_from_IMDB(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
class TheTMDbData(ITheData):
def readMovies(self):
# movies = some_read_from_TMDb(self.genre, self.year_from, self.year_to)
movies= read_test_movies(self.year_from, self.year_to, self.genre)
return movies
We could use also two different formats:
class TheTXTFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.txt').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
for movie in self.readMovies():
line= Template('$title, released on $release').substitute(**movie)
line+= 'n'
strio.write(line)
strio.seek(0)
return strio.read()
class TheCSVFormat(ITheFormat):
def filename(self):
# Here `genre`, `year_from` and `year_to` are unknown
params= 'genre': self.genre, 'year_from': self.year_from, 'year_to': self.year_to
return Template('movies_of_$genre_from_$year_from_to_$year_to.csv').substitute(**params)
def make(self):
# Here `readMovies()` is unknown
strio = PY3 and io.StringIO() or io.BytesIO()
writer = csv.writer(strio, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL)
header = ('Title', 'Release')
writer.writerow(header)
for movie in self.readMovies():
writer.writerow((movie['title'], movie['release']))
strio.seek(0)
return strio.read()
And two different sending channels:
class TheMailSend(ITheSend):
host = 'localhost'
sender = 'movie@spammer.com'
receivers = ['movie@spammed.com']
def send(self):
# Here `make()` is unknown
print('TheMailSend.send() Sending to %s' % str(self.receivers))
try:
message = self.make() # Format agnostic
smtpObj = smtplib.SMTP(self.host)
smtpObj.sendmail(self.sender, self.receivers, message)
return True, 'ok'
except Exception as ss:
return False, str(ss)
class TheWSSend(ITheSend):
url = 'spammed.com/movies/send'
def send(self):
# Here `make()` is unknown
print('TheWSSend.send() Sending to %s' % str(self.url))
try:
content = self.make() # Format agnostic
s= requests.Session()
response= s.post(url= self.url, data= 'content': content)
s.close()
if response.status_code == 200:
return True, 'ok'
else:
return False, response.status_code
except Exception as ss:
return False, str(ss)
So, we could end with some deliverers like these:
class TheIMDBToTXTFile(ITheDeliverer, TheIMDBData, TheTXTFormat):
def __init__(self, year_from, year_to, genre= None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
filepath= os.path.join('/tmp', self.filename())
f= open(filepath, 'w')
f.write(self.make())
f.close()
print('TheIMDBToTXTFile.deliver() => Successfully delivered to %s' % str(filepath))
class TheIMDBToWS(ITheDeliverer, TheIMDBData, TheTXTFormat, TheWSSend):
def __init__(self, year_from, year_to, genre=None):
TheIMDBData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg = self.send()
if ok:
print('TheIMDBToWS.deliver() => Successfully delivered!')
else:
print('TheIMDBToWS.deliver() => Error delivering: %s' % str(msg))
class TheTMDbToMail(ITheDeliverer, TheTMDbData, TheCSVFormat, TheMailSend):
def __init__(self, year_from, year_to, genre=None):
TheTMDbData.__init__(self, year_from, year_to, genre)
def deliver(self):
ok, msg= self.send()
if ok:
print('TheTMDbToMail.deliver() => Successfully delivered!')
else:
print('TheTMDbToMail.deliver() => Error delivering: %s' % str(msg))
And they work fine -with obvious connection errors-:
>>> imdbToTxt = TheIMDBToTXTFile(year_from= 2000, year_to= 2010)
>>> imdbToTxt.deliver()
TheIMDBToTXTFile.deliver() => Successfully delivered to /tmp/movies_of_None_from_200_to_2010.txt
>>>
>>> imdbToWs = TheIMDBToWS(year_from= 2000, year_to= 2010)
>>> imdbToWs.deliver()
TheWSSend.send() Sending to http://spammed.com/movies/send?
TheIMDBToWS.deliver() => Error delivering: 405
>>>
>>> tmdbToMail = TheTMDbToMail(year_from= 1980, year_to= 2019, genre= 'war')
>>> tmdbToMail.deliver()
TheMailSend.send() Sending to ['movie@spammed.com']
TheTMDbToMail.deliver() => Error delivering: [Errno 111] Connection refused
But, as commented, some attributes are unknown for some classes, and the linter is -obviously- complaining about it:
Instance of 'TheTXTFormat' has no 'genre' member
Instance of 'TheTXTFormat' has no 'year_from' member
Instance of 'TheTXTFormat' has no 'year_to' member
Instance of 'TheTXTFormat' has no 'readMovies' member
Instance of 'TheCSVFormat' has no 'genre' member
Instance of 'TheCSVFormat' has no 'year_from' member
Instance of 'TheCSVFormat' has no 'year_to' member
Instance of 'TheCSVFormat' has no 'readMovies' member
Instance of 'TheMailSend' has no 'make' member
Instance of 'TheWSSend' has no 'make' member
So, the question remains: is here multi-inheritance a good model?
The alternatives could be: a derivated-classes model, or just independent classes and passing around parameters like data or formatter. But none of them seem so simple as multi-inheritance (although they'd fix linter -and probably conceptual- problems).
python python-3.x python-2.7 multiple-inheritance
python python-3.x python-2.7 multiple-inheritance
asked Mar 26 at 11:09
AfialapisAfialapis
185 bronze badges
185 bronze badges
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55355703%2fsharing-attributes-between-classes-is-multi-inheritance-right-and-pythonic-he%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Is this question similar to what you get asked at work? Learn more about asking and sharing private information with your coworkers using Stack Overflow for Teams.
Is this question similar to what you get asked at work? Learn more about asking and sharing private information with your coworkers using Stack Overflow for Teams.
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55355703%2fsharing-attributes-between-classes-is-multi-inheritance-right-and-pythonic-he%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown