Question

I have two classes that loosely take the form below:

class Foo:

    def __init__(self, foo):
        self.__foo = foo


class Bar(Foo):

    def bar(self):
        print self.__foo

When I try to invoke the bar method on an instance of Bar, it fails.

b = Bar('foobar')    
b.bar()

Result:

Traceback (most recent call last):
  File "foobar.py", line 14, in <module>
    b.bar()
  File "foobar.py", line 10, in bar
    print self.__foo
AttributeError: Bar instance has no attribute '_Bar__foo'

My understanding is that this code should work based on two other questions, why doesn't it?

Was it helpful?

Solution

Simple. __foo contains 2 underscores in the beginning, so it's assumed to be class-private method and it's transformed into _Classname__method.

When you request access to the attribute named as such on Bar object it asks Bar class if it has this method (not Foo class), so self.__foo is always the same as self._Bar__foo.

From the documentation:

When an identifier that textually occurs in a class definition begins with two or more underscore characters and does not end in two or more underscores, it is considered a private name of that class. Private names are transformed to a longer form before code is generated for them. The transformation inserts the class name, with leading underscores removed and a single underscore inserted, in front of the name. For example, the identifier __spam occurring in a class named Ham will be transformed to _Ham__spam.

If you modify your code slightly

class Foo:
    def __init__(self, foo):
        self.__foo = foo
        assert hasattr(self, '_Foo__foo'), 'Attribute has been just created'


class Bar(Foo):
    def bar(self):
        assert hasattr(self, '_Foo__foo'), 'No errors, thanks to inheritance'

assert statements will not cause any AssertionErrors.

Add __getattribute__ method to Bar class to capture all requests to Bar objects:

class Bar(Foo):

    def bar(self):
        print('Accessing __foo from bar')
        print(self.__foo)

    def __getattribute__(self, name):
        print('Requested', name)
        return super().__getattribute__(name)

b = Bar('foobar')
b.bar()

There will be 3 lines (apart from AttributeError) in the output:

Requested bar
Accessing __foo from bar
Requested _Bar__foo # AttributeError follows

As you can see, if attribute you are requesting has 2 leading underscores, Python is renaming it on the fly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top