0

I have the following code:

class Desc(object):
    @property
    def color_desc(self):
        return 'Color is ' + self.color

    @property
    def brand_desc(self):
        return 'Brand is ' + self.brand

class Text(Desc):
    def __init__(self):
        self.color = 'blue'
        self.brand = 'volvo'

def main():
    t = Text()
    print t.color_desc

if __name__ == '__main__':
    main()

This correctly works and outputs Color is blue when run. However, if I modify the code slightly so that the property is set to an actual Class attribute, as in:

class Desc(object):
    def __init__(self):
        self._color_desc = color_desc
        self._brand_desc = brand_desc

    @property
    def color_desc(self):
        return self._color_desc

    @color_desc.setter
    def color_desc(self):
        self._color_desc = 'Color is ' + self.color

    @property
    def brand_desc(self):
        return self._brand_desc

    @property
    def brand_desc(self):
        self._brand_desc = 'Brand is ' + self.brand


class Text(Desc):
    def __init__(self):
        self.color = 'blue'
        self.brand = 'volvo'

All of a sudden, it errs out with AttributeError: 'Text' object has no attribute '_color_desc'. How come the Text attributes are inherited correctly in the first place but cannot be accessed in the second one. To me, these two solutions seem to do the same thing.

mart1n
  • 5,969
  • 5
  • 46
  • 83

3 Answers3

3

In your Text class, the __init__ is overriding the Desc.__init__ class, so the _color_desc attributes is not initialized.

loutre
  • 874
  • 8
  • 16
3

You need to call the __init__ of the parent class.

Do something like this:

class Text(Desc):
    def __init__(self):
        self.color = 'blue'
        self.brand = 'volvo'
        super(Text, self).__init__(0,0)

The init function of your Desc class is incorrectly define. Do this:

class Desc(object):
    def __init__(self, color_desc, brand_desc):
        self._color_desc = color_desc
        self._brand_desc = brand_desc
DevShark
  • 8,558
  • 9
  • 32
  • 56
  • `super(Text, self).__init__()` should be at the bottom. – Avinash Raj Mar 21 '16 at 13:14
  • I see, so the constructor is overridden. However, adding the `super` statement throws this error: `NameError: global name 'color_desc' is not defined`. – mart1n Mar 21 '16 at 13:20
  • Because it isn't. What value is `Desc.__init__` supposed to use to set `self._color_desc`? – chepner Mar 21 '16 at 13:23
  • @chepner The value set by the setter of each property. – mart1n Mar 21 '16 at 13:25
  • But you're not using the setter; you're assigning directly to the underlying attribute. The setter is only invoked if you assign *to* the name of the property. – chepner Mar 21 '16 at 13:26
  • Please note that I edited out `self.__class__` for a reason - I'm not sure where you heard it was a good idea, but see e.g. http://stackoverflow.com/q/4235078/3001761 for why it's not. – jonrsharpe Mar 21 '16 at 13:26
  • I see, ok, I was not aware of that. – DevShark Mar 21 '16 at 13:32
-1

It's not clear why you thought your second snippet would work at all. It has the following obvious issues:

  • Desc.__init__ is using two variables (color_desc and brand_desc) that aren't actually passed to it - are you expecting them to be in the global scope?
  • You have two (different!) getters, one of which returns something, and no setter for brand_desc.
  • The color_desc setter neither takes nor uses any parameter, so what exactly is it supposed to be setting?
  • The subclass Text.__init__ doesn't call or pass anything to the superclass version, or do the job itself.

I think what you intended was something like:

class Desc(object):

    def __init__(self, color_desc, brand_desc):
        self._color_desc = color_desc
        self._brand_desc = brand_desc

    @property
    def color_desc(self):
        return 'Color is ' + self._color_desc

    @color_desc.setter
    def color_desc(self, new_color):
        self._color_desc = new_color

    @property
    def brand_desc(self):
        return 'Brand is ' + self._brand_desc

    @brand_desc.setter
    def brand_desc(self, new_brand):
        self._brand_desc = new_brand    


class Text(Desc):

    def __init__(self):
        super(Text, self).__init__('blue', 'volvo')

Although now it looks like Text should be an instance, not a subclass, of Desc. You could also simplify by not assigning to e.g. color_desc at all, using a read-only property:

class Desc(object):

    def __init__(self, color, brand):
        self.color = color
        self.brand = brand

    @property
    def color_desc(self):
        return 'Color is ' + self.color

    @property
    def brand_desc(self):
        return 'Brand is ' + self.brand


car = Desc('blue', 'volvo')
car.color = 'red'
print car.color_desc  # Color is red

Now you set the instance's .color directly, but .color_desc still gives you the nicely-formatted version.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Yes, the intended code was as you said. I hurried the example code and missed a thing or two. Anyway, the simplified solution won't do since I specifically need a Car that has-a Description. The code was just an example and in reality the Description class should be reused for e.g. Boats, Trains, etc. – mart1n Mar 21 '16 at 14:58
  • @mart1n if you need *"a Car that has-a Description"* then I think you're looking for **composition**, not **inheritance** (the latter would imply that a Car *is-a* Description, which doesn't seem right). – jonrsharpe Mar 21 '16 at 23:56