Search code examples
pythonoopencapsulation

How to avoid accidentally messing up the base class in Python?


Python uses the underscore convention for private variables. However, nothing seems to stop you from messing up the base class accidentally in, for example

class Derived(Base):
    def __init__(self, ...):
        ...
        super(Derived, self).__init__(...)
        ...
        self._x = ...

if Base also happens to use the name _x.

What are the best practices to avoiding this kind of mistake?

This seems especially challenging, if different people implemented Base and Derived classes, or _x was added to Base after Derived was implemented (So, the implementation of Derived would be breaking the encapsulation retroactively)


Solution

  • Use private variables with two underscores. This way, name mangling protects you from messing up your parent class (in normal use cases).

    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.

    [...] Note that the mangling rules are designed mostly to avoid accidents; it still is possible to access or modify a variable that is considered private. This can even be useful in special circumstances, such as in the debugger.

    Example

    class A(object):
        def __init__(self):
            self.__v = 1
        def __str__(self):
            return "A = {}".format(self.__v)
    
    class B(A):
        def __init__(self):
            A.__init__(self)
            self.__v = 2
        def __str__(self):
            return "{}; B = {}".format(A.__str__(self), self.__v)
    
    a = A()
    b = B()
    print(a)
    print(b)
    

    yields

    A = 1
    A = 1; B = 2