2

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'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
amkleist
  • 181
  • 1
  • 2
  • 11
  • 2
    Repeat after me: Python is **not Java**. Don't use class private names unless you want to avoid subclasses from clashing. There is no concept of method privacy like you have in Python. You don't need to use inner classes to get privileged access. – Martijn Pieters Apr 13 '16 at 14:10
  • 2
    In other words, don't use inner classes, use *single* underscores for API internal methods not to be used by external users. – Martijn Pieters Apr 13 '16 at 14:11
  • It is possible, but not trivial, check the answer at http://stackoverflow.com/questions/2024566/access-outer-class-from-inner-class-in-python – João Pinto Apr 13 '16 at 14:18
  • @JoãoPinto: different problem, albeit stemming from using a nested class too. The OP has avoided that problem already by passing in a reference to the outer class instance. – Martijn Pieters Apr 13 '16 at 14:23

2 Answers2

8

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • That is a very good answer! – but I don't understand how you can conclude that I'm not using double-underscore names as intended by my simple example? I've edited my initial post with a new example and more info. – amkleist Apr 14 '16 at 07:39
  • @amkleist: again, Python does not prevent the user *regardless*. If you use `__val` the user *can still alter `Outer._Outer__val`*. Python is a language for consenting adults; you as a developer can only *document* that the variable is internal. You do so already by using a single leading underscore for the name. – Martijn Pieters Apr 14 '16 at 07:59
  • @amkleist: You can use a `@property` to make an attribute that has no setter. That's already the case for `outer.inner.val`, trying to set that attribute with `outer.inner.val = 'new value'` will fail because there is no setter defined. That's all you need in a properly designed Python API. – Martijn Pieters Apr 14 '16 at 08:03
  • 1
    @amkleist: if a determined user then *still* goes and set the `outer._val` attribute (using `outer._val = 'foo'` for example) then that is *their problem*, not yours. They are then deliberately breaking the API and it is their own fault if that breaks things. – Martijn Pieters Apr 14 '16 at 08:04
  • @Martjin Pieters `class Foo: def __init__(self, val): self._val = val @property def val(self): return self._val foo = Foo('foo') print foo.val foo.val = 'new value' print foo.val` Prints out: foo new-value – amkleist Apr 14 '16 at 08:44
  • 1
    @amkleist: you must inherit from `object` for `property` to work properly: `class Foo(object):`. – Martijn Pieters Apr 14 '16 at 08:46
  • Thank you very much - because without inheritance of object, then I was creating a variable ´Foo.val´. – amkleist Apr 14 '16 at 08:53
  • @amkleist: it is creating an attribute on the instance, yes, because the [`property.__set__` descriptor method](https://docs.python.org/2/howto/descriptor.html) isn't supported on old-style classes. – Martijn Pieters Apr 14 '16 at 08:54
1

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.

aghast
  • 14,785
  • 3
  • 24
  • 56
  • You can still access the name from outside the class. Simply apply the same transformation manually; e.g. `Inner.getVal()` could be fixed up to use `self.__outer._Outer__getVal()`. – Martijn Pieters Apr 13 '16 at 14:28