amkleist
amkleist

Reputation: 191

Python: accessing outer private functions from inner class

Am I missing something, or this something like this not possible?

class Outer:
    def __init__(self, val):
        self.__val = val

    def __getVal(self):
        return self.__val

    def getInner(self):
        return self.Inner(self)

    class Inner:
        def __init__(self, outer):
            self.__outer = outer            

        def getVal(self):
            return self.__outer.__getVal()


foo = Outer('foo')
inner = foo.getInner()
val = inner.getVal()    
print val

I'm getting this error message:

    return self.__outer.__getVal()
AttributeError: Outer instance has no attribute '_Inner__getVal'

Upvotes: 2

Views: 3302

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1122342

You are trying to apply Java techniques to Python classes. Don't. Python has no privacy model like Java does. All attributes on a class and its instances are always accessible, even when using __name double-underscore names in a class (they are simply renamed to add a namespace).

As such, you don't need an inner class either, as there is no privileged access for such a class. You can just put that class outside Outer and have the exact same access levels.

You run into your error because Python renames attributes with initial double-underscore names within a class context to avoid clashing with subclasses. These are called class private because the renaming adds the class names as a namespace; this applies both to their definition and use. See the Reserved classes of identifiers section of the reference documentation:

__*
Class-private names. Names in this category, when used within the context of a class definition, are re-written to use a mangled form to help avoid name clashes between “private” attributes of base and derived classes.

All names with double underscores in Outer get renamed to _Outer prefixed, so __getVal is renamed to _Outer__getVal. The same happens to any such names in Inner, so your Inner.getVal() method will be looking for a _Inner__getVal attribute. Since Outer has no _Inner__getVal attribute, you get your error.

You could manually apply the same transformation to Inner.getVal() to 'fix' this error:

def getVal(self):
    return self.__outer._Outer__getVal()

But you are not using double-underscore names as intended anyway, so move to single underscores instead, and don't use a nested class:

class Outer:
    def __init__(self, val):
        self._val = val

    def _getVal(self):
        return self._val

    def getInner(self):
        return _Inner(self)

class _Inner:
    def __init__(self, outer):
        self._outer = outer            

    def getVal(self):
        return self._outer._getVal()

I renamed Inner to _Inner to document the type is an internal implementation detail.

While we are on the subject, there really is no need to use accessors either. In Python you can switch between property objects and plain attributes at any time. There is no need to code defensively like you have to in Java, where switching between attributes and accessors carries a huge switching cost. In Python, don't use obj.getAttribute() and obj.setAttribute(val) methods. Just use obj.attribute and obj.attribute = val, and use property if you need to do more work to produce or set the value. Switch to or away from property objects at will during your development cycles.

As such, you can simplify the above further to:

class Outer(object):
    def __init__(self, val):
        self._val = val

    @property
    def inner(self):
        return _Inner(self)

class _Inner(object):
    def __init__(self, outer):
        self._outer = outer            

    @property
    def val(self):
        return self._outer._val

Here outer.inner produces a new _Inner() instance as needed, and the Inner.val property proxies to the stored self._outer reference. The user of the instance never need know either attribute is handled by a property object:

>>> outer = Outer(42)
>>> print outer.inner.val
42

Note that for property to work properly in Python 2, you must use new-style classes; inherit from object to do this; on old-style classes on property getters are supported (meaning setting is not prevented either!). This is the default in Python 3.

Upvotes: 8

aghast
aghast

Reputation: 15310

The leading-double-underscore naming convention in Python is supported with "name mangling." This is implemented by inserting the name of the current class in the name, as you have seen.

What this means for you is that names of the form __getVal can only be accessed from within the exact same class. If you have a nested class, it will be subject to different name mangling. Thus:

class Outer:
    def foo(self):
        print(self.__bar)

    class Inner:
        def foo2(self):
            print(self.__bar)

In the two nested classes, the names will be mangled to _Outer__bar and _Inner__bar respectively.

This is not Java's notion of private. It's "lexical privacy" (akin to "lexical scope" ;-).

If you want Inner to be able to access the Outer value, you will have to provide a non-mangled API. Perhaps a single underscore: _getVal, or perhaps a public method: getVal.

Upvotes: 1

Related Questions