code base 5000
code base 5000

Reputation: 4102

Inheritance issue when using super() (Python)

below is two classes showing inheritance for an API I am working on. So I want the base class, ServiceTemplateTest, to have a common set of properties for all services and to behave like an OrderedDict object. So the base class inherirts from OrderedDict. I then do a super() in the __init__ in order to clear up the MRO issue. Now when I actually use this base class, I am getting issues when I try to class __init() from the base class. According to my debugger it's saying I need to call: self._ServiceTemplateTest__init(), but shouldn't it just be __init() since I call super()? What is the proper way to allow me to inherit without have to do this call: self._ServiceTemplateTest__init()?

Do I need to create an __init__() on the non-base classes where I have multiple super() calls? If so, what super class should come first?

Thank you for any advice you can provide!

from collections import OrderedDict
import urllib2, json, urllib
class ServiceTemplateTest(OrderedDict):
    _con = None
    _url = None
    def __init__(self, url, connection=None, initialize=False, **kwargs):
        super(ServiceTemplateTest, self).__init__()
        self._url = url
        self._con = connection
        if initialize:
            self.__init(connection)
    def __init(self, connection=None):
        if connection is None:
            connection = self._con
        attributes = [attr for attr in dir(self)
                      if not attr.startswith('__') and \
                      not attr.startswith('_')]
        params = {"f":"json"}
        params = urllib.urlencode(params)
        result = json.loads(
            urllib2.urlopen(url="{url}?{params}".format(url=self._url,
                                                        params=params)).read())

        for k,v in result.items():
            if k in attributes:
                setattr(self, "_"+ k, v)
                self[k] = v
            else:
                self[k] = v
        self.__dict__.update(result)
    #----------------------------------------------------------------------
    @property
    def connection(self):
        return self._con
    #----------------------------------------------------------------------
    @connection.setter
    def connection(self, value):
        self._con = value
        self.refresh()
    #----------------------------------------------------------------------
    @property
    def url(self):
        return self._url
    #----------------------------------------------------------------------
    @url.setter
    def url(self, value):
        """"""
        self._url = value
        self.refresh()
    #----------------------------------------------------------------------
    def __str__(self):
        return json.dumps(self)
    #----------------------------------------------------------------------
    def __repr__(self):
        return self.__str__()
    #----------------------------------------------------------------------
    def refresh(self):
        self.__init()

class SchematicService(ServiceTemplateTest):
    """schematic service"""
    _con = None
    _json_dict = None
    _json = None
    _url = None
    _nbSchematicLayers = None
    _nbTemplates = None
    _type = None
    _name = None
    _nbEstimatedDiagrams = None
    def __init__(self, url, connection=None, initialize=False, **kwargs):
        super(SchematicService, self).__init__(url=url, connection=connection,
                                               initialize=initialize, **kwargs)
        self._url = url
        self._con = connection
        if initialize:
            self.__init(connection)
    #----------------------------------------------------------------------
    @property
    def nbSchematicLayers(self):
        if self._nbSchematicLayers is None:
            self.__init()
        return self._nbSchematicLayers
    #----------------------------------------------------------------------
    @property
    def nbTemplates (self):
        if self._nbTemplates  is None:
            self.__init()
        return self._nbTemplates
    #----------------------------------------------------------------------
    @property
    def type(self):
        if self._type  is None:
            self.__init()
        return self._type
    #----------------------------------------------------------------------
    @property
    def name(self):
        if self._name is None:
            self.__init()
        return self._name
    #----------------------------------------------------------------------
    @property
    def nbEstimatedDiagrams(self):
        if self._nbEstimatedDiagrams is None:
            self.__init()
        return self._nbEstimatedDiagrams
    @property
    def somerandompropertytest(self):
        return "hi"
if __name__ == "__main__":
    url = "http://servicesbeta6.esri.com/arcgis/rest/services/S1_Schematics/MapServer"
    s = SchematicService(url=url, initialize=True)
    print s

Upvotes: 0

Views: 81

Answers (2)

bruno desthuilliers
bruno desthuilliers

Reputation: 77902

dcrosta already answered (mainly the same thing as I was about to post) on the problem with calling __init from a subclass. I just wanted to add that in your code example, the whole SchematicService.__init__() is just useless, as it will only redo what ServiceTemplateTest.__init__() already done.

Also, having both class attributes and instance attributes with the same names doesn't help wrt/ readability / maintainability. If these are intended to be defaults for not-yet-set instance attributes, it's better to set them as instance attributes in the __init__().

Upvotes: 1

dcrosta
dcrosta

Reputation: 26258

The issue isn't inheritance or super(), but that you're trying to call a "private" method from outside of the class. Any method whose name begins with two underscores -- your __init() in this case -- are private to the class they're defined in.

Python doesn't really have "private" in the sense you might be familiar with from other OO languages, instead it does something called name mangling to make it inconvenient, rather than impossible. In essence, if you name a method like __init(), Python will convert that to a method named _NameOfClass__init(), and will do the same in calls (or access to attributes) with similar naming. The trick is, the "NameOfClass" part is always the name of the class you're accessing the method from -- the subclass, SchematicService, in your case. Since the names don't match, Python can't find the method.

Of course, in Python nothing is actually really private. You can access the private method by mangling its name yourself, if you want. The conventional wisdom, though, is generally not to use double-underscore private methods or attributes at all. By convention, if you want to have a method on a base class that is not meant to be called from outside of the base class (eg because it isn't part of the public API you want to support), name it with a single leading underscore. Pythonistas know that this means, "the signature, purpose, or even presence of this method or attribute might go away at a later date, and I should not rely on it".

My $0.02: If you intend the method to be callable from anywhere -- both in subclasses and other, unrelated code -- make it a regular public method (no leading underscores in the name); if you only want it accessible to subclasses, use a single leading underscore.

Upvotes: 2

Related Questions