4

Why this simple code doesn't work for Python 2.7 ? Please, help. Most likely I misuse super method in 'New Style' for classes.

class Mechanism(object):
    def __init__(self):
        print('Init Mechanism')
        self.__mechanism = 'this is mechanism'

    def get_mechanism(self):
        return self.__mechanism

class Vehicle(object):
    def __init__(self):
        print('Init Vehicle')
        self.__vehicle = 'this is vehicle'

    def get_vehicle(self):
        return self.__vehicle

class Car(Mechanism, Vehicle):
    def __init__(self):
        super(Car, self).__init__()

c = Car()
print(c.get_mechanism())
print(c.get_vehicle())

The error:

Init Vehicle
Traceback (most recent call last):
  File "check_inheritance.py", line 22, in <module>
    print(c.get_mechanism())
  File "check_inheritance.py", line 7, in get_mechanism
    return self.__mechanism
AttributeError: 'Car' object has no attribute '_Mechanism__mechanism'

EDIT

  1. Fixed def __init(self): in Mechanism class onto def __init__(self):
  2. The correct answer is to use super method in all classes. Not only in Car class. See the answer of Martijn Pieters
  3. Try to avoid double underscore __ for private variables. It is not a Python way (style of code). See the discussion for more info here.
FooBar167
  • 2,721
  • 1
  • 26
  • 37
  • 1
    your mechanism init is spelled wrong, should have two underscores before and after – Brandon H. Gomes Dec 18 '17 at 18:08
  • There's also a misuse of `super` here: as currently structured, the `Vehicle.__init__` method won't get called. You need a common root class (a base class for both `Vehicle` and `Mechanism`), and all subclasses, including `Vehicle` and `Mechanism` should call `super`. – Mark Dickinson Dec 18 '17 at 18:17
  • @MartijnPieters: I think there's a valid question about use of super here, beyond the misspelled `__init__` method. – Mark Dickinson Dec 18 '17 at 18:21
  • In this case, you simply have a typo, and you forgot to call the next `__init__` method in the chain, so the initializer for `Mechanism` is not called for two separate reasons. You really should not be using leading-double-underscore attributes unless you are building a framework for 3rd-parties to extend on, see [Inheritance of private and protected methods in Python](//stackoverflow.com/q/20261517) – Martijn Pieters Dec 18 '17 at 18:21
  • You must call `super` in **all** child classes for co-operative multi-inheritance to work properly. Then the private variables will work just fine. – ekhumoro Dec 18 '17 at 18:25
  • Yes, usage of `super` in all classes for proper inheritance is vital in the New Style classes. – FooBar167 Dec 18 '17 at 18:54

1 Answers1

3

You have 2 issues:

  • You misnamed the __init__ method of Mechanism; you are missing two underscores.

  • Your __init__ methods do not cooperate correctly in a multiple inheritance situation. Make sure you always call super(...).__init__(), in all your __init__ methods.

The following code works:

class Mechanism(object):
    def __init__(self):
        super(Mechanism, self).__init__()
        print('Init Mechanism')
        self.__mechanism = 'this is mechanism'

    def get_mechanism(self):
        return self.__mechanism

class Vehicle(object):
    def __init__(self):
        super(Vehicle, self).__init__()
        print('Init Vehicle')
        self.__vehicle = 'this is vehicle'

    def get_vehicle(self):
        return self.__vehicle

class Car(Mechanism, Vehicle):
    def __init__(self):
        super(Car, self).__init__()

Demo:

>>> c = Car()
Init Vehicle
Init Mechanism
>>> print(c.get_mechanism())
this is mechanism
>>> print(c.get_vehicle())
this is vehicle

You should probably also not use double-underscore names. See Inheritance of private and protected methods in Python for the details, but the short reason is that you do not have a use case here for class-private names, as you are not building a framework meant to be extended by third parties; that's the only real usecase for such names.

Stick to single-underscore names instead, so _mechanism and _vehicle.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ah! I see. I have to use `super(...).__init__()` in ALL classes. Thank you, Martijn! Your change is working. – FooBar167 Dec 18 '17 at 18:35
  • To Martijn -- Maybe I'm stuck in C++ style, but my opinion is to make all variables private inside class. Am I not right? – FooBar167 Dec 18 '17 at 18:44
  • @foobar: you are indeed stuck in C++ style. Python doesn't **have** a privacy model; **all** attributes are available to all code running in the interpreter. Class 'private' attributes only given an automatic namespace, nothing more. That automatic namespace (the classname with a single leading underscore added) is there to prevent clashes with attributes in subclasses, not to make the names inaccessible from the outside. – Martijn Pieters Dec 18 '17 at 19:24
  • @foobar: The Python style is to not use accessors, and to make attributes be the first-class public API instead. So instead of `c.get_mechanism()`, just use `c.mechanism` (a public attribute). – Martijn Pieters Dec 18 '17 at 19:25
  • You're right. I'll google this question. And most likely I'll change double underscores onto single underscores for "private" variables... and get rid of `getters` and `setters`. – FooBar167 Dec 18 '17 at 19:31
  • 1
    @foobar: I don't know why C++ prefers getters and setters, but I do know that in *Java* it is really hard to transition an API from public attributes to getters and setters later on. So to avoid that transition pain, the guideline is to always use getters and setters, regardless. In Python, converting an attribute to a `property` object (the Pythonic getter-setter pattern) is trivial, so you never have to worry about that future choice. – Martijn Pieters Dec 18 '17 at 19:33
  • Encapsulation is a principle of Object Oriented Programming. Manually prohibit acces to internal class properties. But maybe it is useless in Python. – FooBar167 Dec 18 '17 at 19:41
  • @foobar: Python expects everyone to be responsible adults. Use `_name` for attributes that are part of the internal implementation, not the public API. They are not inaccessable, just clearly 'documented' as being internal and whomever tries to access these anyway will have to accept the maintenance risk that the attribute is gone in a future release. `Mechanism.mechanism` is not a private attribute, it is part of the public API; adding indirection via a getter doesn't make it any less so. – Martijn Pieters Dec 18 '17 at 19:45
  • the use of "object" here in the base class, i.e., "class Vehicle(object)" instead of "class Vehicle()" is critical, don't overlook it. Otherwise, the original problem will persist (as it did for me as I tried to make this answer work, but couldn't because I had omitted the object base class declaration in my base classes) – slogan621 Nov 05 '20 at 01:09
  • @slogan621: only in Python 2, as inheriting from `object` makes it a new-style class. In Python 3 all classes are new-style classes. `super()` only works in new-style classes. – Martijn Pieters Nov 05 '20 at 11:47