Python's name mangling

Python's name mangling

September 18, 2021 | testing, python


Python’s convention of prefixing a name with an underscore (_handle_error()) is treated as a non-public part of the API by the community, but we can still access it if we want. This Python convention also eases the work when unit-testing these methods.

class Account:
    def name(self):
        return self._name

    @property
    def _name(self):
        return "Anonymous"

acc = Account()

print(acc.name())
print(acc._name)
Anonymous
Anonymous

But what happens when we use two underscores (__handle_error())?

class Account:
    def name(self):
        return self.__name

    @property
    def __name(self):
        return "Anonymous"

acc = Account()

print(acc.name())
print(acc.__name)
Anonymous
Traceback (most recent call last):
  File "<stdin>", line 12, in <module>
AttributeError: 'Account' object has no attribute '__name'

Although Python has limited support to such mechanisms, it does do some name mangling to provide such functionality. We can still access the __name= property by calling the property through its mangled name.

class Account:
    def name(self):
        return self.__name

    @property
    def __name(self):
        return "Anonymous"

acc = Account()

print(acc.name())
print(acc._Account__name)
Anonymous
Anonymous

Since there is a valid use-case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, as long as it occurs within the definition of a class.

https://docs.python.org/3/tutorial/classes.html#private-variables

The mangled name is in the form of __<classname>__<name>. The name mangling helps let subclasses override methods without breaking intraclass method calls.

What about testing?

Now things are much easier when we want to test a doubled underscore method/property: use the _classname_name attribute of the object:

class Account:
    def name(self):
        return self.__name

    @property
    def __name(self):
        return "Anonymous"

# somewhere in your test..
# ..

acc = Account()
assert acc.name() == "Anonymous" # passes
assert acc._Account__name == "Anonymous" # pass

Cython

Python seems to define the mangling object in the pycore_compile.h header and implement it in the compile.c file, enabling us to access through the ctype library like so:

from ctypes import pythonapi, py_object
py_mangle = pythonapi._Py_Mangle
py_mangle.argtypes = py_object, py_object
py_mangle.restype = py_object

print(py_mangle('MyClass', '__privmethod'))

Further, the symbol lookup seems to happen in the symtable_lookup function.


Go to random page