Home > Software engineering >  Python name mangling for private class (with double underscore)
Python name mangling for private class (with double underscore)

Time:05-05

How does the name mangling for a private class (class __ThisClass: pass) works, if we want to use the class inside a @staticmethod

I am able to access it with __class__ (The class to which a class instance belongs). However, is there a way to use a mangled name?

class PublicClass:
    @staticmethod
    def foo(n):
        if n>10:
            return n
        return PublicClass.foo(100) # <---

pub_obj = PublicClass()
print(pub_obj.foo(1), pub_obj.foo(11))

output: 100 11

Here PublicClass.foo(100) can be used (or even __class__)

class __PrivateClass:
    @staticmethod
    def foo(n):
        if n>10:
            return n
        return __class__.foo(100) # <---

pri_obj = __PrivateClass()
print(pri_obj.foo(1), pri_obj.foo(11))

output: 100 11

if we use __PrivateClass.foo(100) below exception is raised

class __PrivateClass:
    @staticmethod
    def foo(n):
        if n>10:
            return n
        return __PrivateClass.foo(100) # <---

NameError: name '_PrivateClass__PrivateClass' is not defined

if we use _PrivateClass__PrivateClass.foo(100) below exception is raised (replace the line# <---)

NameError: name '_PrivateClass__PrivateClass' is not defined

Ask:

  1. How does the name mangling works for a private class?
  2. Should we (try to) never use the double underscore for the class name?

Note: Tried on Python 3.6.13 and Python 3.8.12

CodePudding user response:

Answering first question, in python you can't actually enforce privacy, the point is that it's only suggestion to be private. The python convention says to sugest private method/class use an _ (underscore) and double __ (underscore) is pseudo privacy that won't allow you to call it directly but still will be possibility to workaround it. About your example it doesn't make sense to call __PrivateClass.foo(100) inside this class, better would be just use self.foo(100) and not making it static method.

Answering second question, using underscore for anything is only suggestion that it should be private you can still call it like in example below:

class __PrivateClass:
    def foo(self, n):
        if n>10:
            return n
        return self.foo(5)


print(__PrivateClass().foo(100))

at the end use underscore only if you want something to be called only inside it's scope as a suggestion, have it in mind you can still call it outside but you shouldn't, probably there was some reason why it's private.

CodePudding user response:

As suggested in Python docs

This class object is the one that will be referenced by the zero-argument form of super(). __class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.

When a name is not bound, an attempt to evaluate it raises a NameError exception. Private name mangling: 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. This transformation is independent of the syntactical context in which the identifier is used. If the transformed name is extremely long (longer than 255 characters), implementation defined truncation may happen. If the class name consists only of underscores, no transformation is done.

So whenever you are trying to call foo() method with __PrivateClass.foo() it automatically inserts the class name with it _PrivateClass__PrivateClass

But whenever you are using __class__, this is resolved to <class '__main__.__PrivateClass'> so we are not accessing attribute with class name instead we are accessing it as an object in current module.

Instead of using __class__ you can also access it like this:

import sys
class __PrivateClass:

    @staticmethod
    def foo(n):
        if n>10:
            return n
        print(__class__)
        print(getattr(sys.modules[__name__], '__PrivateClass'))
        print(__class__ is getattr(sys.modules[__name__], '__PrivateClass')) # This will return True so you got it that __class__ is nothing but the current module and then __PrivateClass as its object
        return __PrivateClass.foo(100) # <--- This line is raising exception because whenever we use __ inside class anywhere then that is transformed to **name_of_class_with_leading_underscore_stripped and single underscore appended** -> "_PrivateClass" and then double underscore and then the attribute name.

pri_obj = __PrivateClass()
print(pri_obj.foo(1), pri_obj.foo(11))

Also there is no such private class in python because when you insert double underscore in front of class then this will not affect any thing until you are not importing it somewhere

Also if you do the above thing using methods then also the same error will be there so there is nothing special to use staticmethod here

Let me know if this clears your doubt.

  • Related