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;








2















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










share|improve this question




























    2















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










    share|improve this question
























      2












      2








      2








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










      share|improve this question














      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






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Mar 26 at 11:09









      AfialapisAfialapis

      185 bronze badges




      185 bronze badges






















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



          );













          draft saved

          draft discarded


















          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.



















          draft saved

          draft discarded
















































          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          SQL error code 1064 with creating Laravel foreign keysForeign key constraints: When to use ON UPDATE and ON DELETEDropping column with foreign key Laravel error: General error: 1025 Error on renameLaravel SQL Can't create tableLaravel Migration foreign key errorLaravel php artisan migrate:refresh giving a syntax errorSQLSTATE[42S01]: Base table or view already exists or Base table or view already exists: 1050 Tableerror in migrating laravel file to xampp serverSyntax error or access violation: 1064:syntax to use near 'unsigned not null, modelName varchar(191) not null, title varchar(191) not nLaravel cannot create new table field in mysqlLaravel 5.7:Last migration creates table but is not registered in the migration table

          용인 삼성생명 블루밍스 목차 통계 역대 감독 선수단 응원단 경기장 같이 보기 외부 링크 둘러보기 메뉴samsungblueminx.comeh선수 명단용인 삼성생명 블루밍스용인 삼성생명 블루밍스ehsamsungblueminx.comeheheheh

          155 수학 과학 기타 둘러보기 메뉴eh추가해eh문서를 완성해