"Private" (implementation) class in Python

PythonAccess Modifiers

Python Problem Overview


I am coding a small Python module composed of two parts:

  • some functions defining a public interface,
  • an implementation class used by the above functions, but which is not meaningful outside the module.

At first, I decided to "hide" this implementation class by defining it inside the function using it, but this hampers readability and cannot be used if multiple functions reuse the same class.

So, in addition to comments and docstrings, is there a mechanism to mark a class as "private" or "internal"? I am aware of the underscore mechanism, but as I understand it it only applies to variables, function and methods name.

Python Solutions


Solution 1 - Python

Use a single underscore prefix:

class _Internal:
    ...

This is the official Python convention for 'internal' symbols; "from module import *" does not import underscore-prefixed objects.

Reference to the single underscore convention.

Solution 2 - Python

In short:

  1. You cannot enforce privacy. There are no private classes/methods/functions in Python. At least, not strict privacy as in other languages, such as Java.

  2. You can only indicate/suggest privacy. This follows a convention. The Python convention for marking a class/function/method as private is to preface it with an _ (underscore). For example, def _myfunc() or class _MyClass:. You can also create pseudo-privacy by prefacing the method with two underscores (for example, __foo). You cannot access the method directly, but you can still call it through a special prefix using the classname (for example, _classname__foo). So the best you can do is indicate/suggest privacy, not enforce it.

Python is like Perl in this respect. To paraphrase a famous line about privacy from the Perl book, the philosophy is that you should stay out of the living room because you weren't invited, not because it is defended with a shotgun.

For more information:

Solution 3 - Python

Define __all__, a list of names that you want to be exported (see documentation).

__all__ = ['public_class'] # don't add here the 'implementation_class'

Solution 4 - Python

A pattern that I sometimes use is this:

Define a class:

class x(object):
    def doThis(self):
        ...
    def doThat(self):
        ...

Create an instance of the class, overwriting the class name:

x = x()

Define symbols that expose the functionality:

doThis = x.doThis
doThat = x.doThat

Delete the instance itself:

del x

Now you have a module that only exposes your public functions.

Solution 5 - Python

The convention is prepend "_" to internal classes, functions, and variables.

Solution 6 - Python

To address the issue of design conventions, and as chroder said, there's really no such thing as "private" in Python. This may sound twisted for someone coming from C/C++ background (like me a while back), but eventually, you'll probably realize following conventions is plenty enough.

Seeing something having an underscore in front should be a good enough hint not to use it directly. If you're concerned with cluttering help(MyClass) output (which is what everyone looks at when searching on how to use a class), the underscored attributes/classes are not included there, so you'll end up just having your "public" interface described.

Plus, having everything public has its own awesome perks, like for instance, you can unit test pretty much anything from outside (which you can't really do with C/C++ private constructs).

Solution 7 - Python

Use two underscores to prefix names of "private" identifiers. For classes in a module, use a single leading underscore and they will not be imported using "from module import *".

class _MyInternalClass:
    def __my_private_method:
        pass

(There is no such thing as true "private" in Python. For example, Python just automatically mangles the names of class members with double underscores to be __clssname_mymember. So really, if you know the mangled name you can use the "private" entity anyway. See here. And of course you can choose to manually import "internal" classes if you wanted to).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionoparisyView Question on Stackoverflow
Solution 1 - PythonFerdinand BeyerView Answer on Stackoverflow
Solution 2 - PythonKarl FastView Answer on Stackoverflow
Solution 3 - PythonUncleZeivView Answer on Stackoverflow
Solution 4 - PythonthellerView Answer on Stackoverflow
Solution 5 - PythonBenjamin PetersonView Answer on Stackoverflow
Solution 6 - PythonDimitri TcaciucView Answer on Stackoverflow
Solution 7 - PythonchroderView Answer on Stackoverflow