aviro
aviro

Reputation: 214

Inheriting class attribute with double underscore

I think I understand the concept of "name mangling" in python, but there's something that I probably missed. Take a look at the following code:

#!/usr/bin/env python

class Base(object):
    __data = "Base"

    @classmethod
    def func(cls):
        return "Class name is {}, data is {}".format(cls.__name__, cls.__data)


class A(Base):
    __data = "A"


class B(A):
    __data = "B"


print Base.func()
print A.func()
print B.func()

Here the output I get:

Class name is Base, data is Base
Class name is A, data is Base
Class name is B, data is Base

Now, I understand that for each class the actual name of the class attribute is mangled to _<Class name>__data. So for instance, for Base it would be _Base__data, for A it would be _A__data, etc.

My question is, inside func it identifies correctly the names of the inherited classes (Base, A and B), but cls.__data always leads to cls._Base__data. Why is that? I mean, if __name__ is A or B, then I know I'm inside class A or B, so I expect cls.__data to be the one of A or B respectively. What am I missing here?

Upvotes: 3

Views: 2962

Answers (2)

jsbueno
jsbueno

Reputation: 110476

You are not "missing", to the contrary, you just "found out" what name mangling does: it is made to ensure variables with double underscores inside a method will always see the attribute defined in the same class as that method, and in none of its subclasses.

If you simply want to use the attribute as it is overriden in each subclass, that is the normal behavior for all other attributes, but for the ones prefixed by two underscores.

So, what happens is that the .__data name used inside func is itself mangled, at compile time, to _base__data.

OrderedDict

Python's collections.OrderedDict have an extra trap: Python offers both a pure-python implementation, which uses the __ for its "private attributes", as explained above, but also have a native code implementation in C, and the private structures of that are not exposed to Python.

And the collections module ends the OrderedDict code block with these lines:

try:
    from _collections import OrderedDict
except ImportError:
    # Leave the pure Python version in place.
    pass

That is: the normal "collections.OrderedDict" is written in C, with a lot of opaque structures, that can't be tapped in by subclasses.

The only way to have access to the Python defined OrderedDict is by deleting the _collections.OrderedDict attribute (in the _collections, not collections module), and reload the collections module.

If you do that, and instantiate one ordered dict, the private data structures can be seem:


from imp import reload
import _collections, collections
backup = _collections.OrderedDict
del _collections.OrderedDict
collections = reload(collections)
PyOrderedDict = collections.OrderedDict
_collections.OrderedDict = backup
a  = PyOrderedDict()
dir(a)

Out[xx]: 
['_OrderedDict__hardroot',
 '_OrderedDict__map',
 '_OrderedDict__marker',
 '_OrderedDict__root',
 '_OrderedDict__update',
 '__class__',
 '__contains__',
 '__delattr__',
 ...
]

Upvotes: 6

Arthur Tacca
Arthur Tacca

Reputation: 9998

As you have noticed, the name used for name mangling is the name of the class where a method is declared, not the derived type of the current object.

The documentation for this feature explicitly gives an example about protecting a variable from a derived class (rather than from external code using an instance variable).

Name mangling is helpful for letting subclasses override methods without breaking intraclass method calls. For example:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

Upvotes: 1

Related Questions