Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Python require intimate knowledge of all classes in the inheritance chain?

Python classes have no concept of public/private, so we are told to not touch something that starts with an underscore unless we created it. But does this not require complete knowledge of all classes from which we inherit, directly or indirectly? Witness:

class Base(object):
    def __init__(self):
        super(Base, self).__init__()
        self._foo = 0

    def foo(self):
        return self._foo + 1

class Sub(Base):
    def __init__(self):
        super(Sub, self).__init__()
        self._foo = None

Sub().foo()

Expectedly, a TypeError is raised when None + 1 is evaluated. So I have to know that _foo exists in the base class. To get around this, __foo can be used instead, which solves the problem by mangling the name. This seems to be, if not elegant, an acceptable solution. However, what happens if Base inherits from a class (in a separate package) called Sub? Now __foo in my Sub overrides __foo in the grandparent Sub.

This implies that I have to know the entire inheritance chain, including all "private" objects each uses. The fact that Python is dynamically-typed makes this even harder, since there are no declarations to search for. The worst part, however, is probably the fact Base might inherit from object right now, but in some future release, it switches to inheriting from Sub. Clearly if I know Sub is inherited from, I can rename my class, however annoying that is. But I can't see into the future.

Is this not a case where a true private data type would prevent a problem? How, in Python, can I be sure that I'm not accidentally stepping on somebody's toes if those toes might spring into existence at some point in the future?

EDIT: I've apparently not made clear the primary question. I'm familiar with name mangling and the difference between a single and a double underscore. The question is: how do I deal with the fact that I might clash with classes whose existence I don't know of right now? If my parent class (which is in a package I did not write) happens to start inheriting from a class with the same name as my class, even name mangling won't help. Am I wrong in seeing this as a (corner) case that true private members would solve, but that Python has trouble with?

EDIT: As requested, the following is a full example:

File parent.py:

class Sub(object):
    def __init__(self):
        self.__foo = 12
    def foo(self):
        return self.__foo + 1
class Base(Sub):
    pass

File sub.py:

import parent
class Sub(parent.Base):
    def __init__(self):
        super(Sub, self).__init__()
        self.__foo = None
Sub().foo()

The grandparent's foo is called, but my __foo is used.

Obviously you wouldn't write code like this yourself, but parent could easily be provided by a third party, the details of which could change at any time.

like image 361
Chris Avatar asked Jul 24 '12 21:07

Chris


People also ask

Is inheritance necessary in Python?

Everything in Python is an object. Modules are objects, class definitions and functions are objects, and of course, objects created from classes are objects too. Inheritance is a required feature of every object oriented programming language.

Does Python support multiple inheritance?

Yes, Python supports multiple inheritance. Like C++, a class can be derived from more than one base classes in Python. This is called Multiple Inheritance.

What is inheritance most useful for in Python?

Inheritance allows us to define a class that inherits all the methods and properties from another class. Parent class is the class being inherited from, also called base class. Child class is the class that inherits from another class, also called derived class.

How do you inherit from one class to another in Python?

To create a class that inherits from another class, after the class name you'll put parentheses and then list any classes that your class inherits from. In a function definition, parentheses after the function name represent arguments that the function accepts.


1 Answers

Use private names (instead of protected ones), starting with a double underscore:

class Sub(Base):
    def __init__(self):
        super(Sub, self).__init__()
        self.__foo = None
        #    ^^

will not conflict with _foo or __foo in Base. This is because Python replaces the double underscore with a single underscore and the name of the class; the following two lines are equivalent:

class Sub(Base):
    def x(self):
        self.__foo = None # .. is the same as ..
        self._Sub__foo = None

(In response to the edit:) The chance that two classes in a class hierarchy not only have the same name, but that they are both using the same property name, and are both using the private mangled (__) form is so minuscule that it can be safely ignored in practice (I for one haven't heard of a single case so far).

In theory, however, you are correct in that in order to formally verify correctness of a program, one most know the entire inheritance chain. Luckily, formal verification usually requires a fixed set of libraries in any case.

This is in the spirit of the Zen of Python, which includes

practicality beats purity.

like image 191
phihag Avatar answered Nov 10 '22 20:11

phihag