2

From the docs:

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.

My interpretation:

  1. Mangling the name of a parent class method parent.__m(a, b) to allow a child to overload it with extra parameters child.m(a, b, c). This way when you call child.m(1, 2, 3), the extra parameter isn't being passed to the parent class and confusing it.

  2. Mangling isn't necessary if you plan on keeping the same method signature but changing some of the internal functionality. You can still access the old functionality by using super().

  3. In summary if you want the ability to overload a class method in the future, mangle it. Otherwise, it's not necessary.

Question:

Is my summary correct? The docs are poorly written. Lots of run-on sentences and midstream (asides) that muddle my ability to determine if I've understood correctly.

Edit: I just played with some code:

class Parent( object ):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        
    def method( self ):
        return 1

class Child(Parent):
    def __init__(self, a, b, c ):
        super().__init__(a, b)

    def method(self, c):
        field = [c]
        return field
    

a, b, c = 0, 1, 2
c = Child(a, b, c)
print(c)

This works well enough. The only issue I ran into is if I do something like this:

class Parent( object ):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.c = self.method()
        
    def method( self ):
        return 1

class Child(Parent):
    def __init__(self, a, b, c ):
        super().__init__(a, b)
        self.c = self.method(c)

    def method(self, c):
        field = [c]
        return field
    

a, b, c = 0, 1, 2
c = Child(a, b, c)

Which returns

TypeError: method() missing 1 required positional argument: 'c'

This is discussed in this answer: Python, Overriding an inherited class method

So at the end of the day it looks like I still don't understand what the purpose is.

rocksNwaves
  • 5,331
  • 4
  • 38
  • 77
  • 1
    You don't need mangling to overload/override, since the methods are defined in order. The definition in the subclass will be the only valid definition. If the signature is different, it will still work, though linters might not like it – DeepSpace Feb 28 '21 at 17:13
  • Yes, I just added an edit that I had discovered that. I see what you are saying after playing with some code. – rocksNwaves Feb 28 '21 at 17:16

1 Answers1

1

Name mangling is unnecessary for a child class to override a method with more parameters. The purpose is to prevent overriding of a method, particularly the overriding of a "private" method that serves some internal purpose for the class and whose behaviour should not be changed by subclasses.

The idea is that if a subclass declares a method of the "same" name, it will be mangled to a different one to avoid overriding the superclass's method. Compare and contrast with Java, where a private method cannot be overridden or even called from within the subclass.

class A:
    def foo(self):
        self.bar()
        self.__baz()
    def bar(self):
        print('A.bar')
    def __baz(self):
        print('A.__baz')

class B(A):
    def bar(self):
        print('B.bar')
    def __baz(self):
        print('B.__baz')

B().foo()

Output:

B.bar
A.__baz

Note that this is about overriding, not overloading. Python does not have method overloading, the number or types of the arguments in a method call are not used to determine which method is invoked; only the method name is used for that.

kaya3
  • 47,440
  • 4
  • 68
  • 97
  • Okay, I think I understand... In your arbitrary example, `A.__baz` has some functionality that you don't want to lose if the same name is used in B? So the mangling protects that method. – rocksNwaves Feb 28 '21 at 17:21
  • @rocksNwaves Exactly. – kaya3 Feb 28 '21 at 17:22
  • And a real world use case would be if `A.__baz` was used in some other parent class method, say `def booboo(): return self.__baz()`. In this case `A.booboo()` relies on the fact that a child class hasn't stolen the `baz` name??? Sorry, the foo, bar baz make it hard to relate to a real world use case. Too abstract for me to hold it all in my head. – rocksNwaves Feb 28 '21 at 17:23
  • 1
    Yes, in this example `foo` is that method. Consider for example a priority queue class which uses a "private" heap, with "private" `__sift_up` and `__sift_down` methods to help when adding and removing from the heap. If a subclass changed what those methods did, the class would break. – kaya3 Feb 28 '21 at 17:25
  • Cheers. So mangle to maintain parent class functionality for a given name. You've cleared up a big misunderstanding. Have a great day! – rocksNwaves Feb 28 '21 at 17:27