Private Methods in Python: Do They Actually Exist?

Does Python have private methods in the same way other programming languages do? In this Python tutorial, we will answer this question.

In Python, you can define a private method by prefixing the method name with a single underscore. Differently from other programming languages making a method private in Python doesn’t prevent you from accessing it from outside your class. It’s simply a convention to tell other developers that the method is for “internal use only” for your class.

We will go through a few examples that will show you how this works…

…and you will also see why private methods in Python are not truly private!

Let’s start this object-oriented tutorial!

What is a Private Method in Python?

A private method is a method that should only be called inside the Python class where it is defined. To indicate a Python private method prefix its name with a single underscore.

I say that it “should” only be called inside the class where it’s defined because this is not something that is enforced by the Python interpreter.

Let’s see how you can define a private method in Python and how it differs from a public method.

We will start by defining a class that has one public method:

class Person:
    def __init__(self, name):
        self.name = name

    def run(self):
        print("{} is running".format(self.name))

When we create an instance and execute the run() method we get back the expected message:

jack = Person("Jack")
jack.run()

[output]
Jack is running

A public method defined in a Python class can be called on an instance of that class.

Now let’s say we want to add a warmup() method that the run method calls internally. At the same time, the warmup() method shouldn’t be callable outside of the class.

Is that possible?

In theory, we can achieve this by adding one underscore before the name of the method:

def run(self):
    self._warmup()
    print("{} is running".format(self.name))

def _warmup(self):
    print("{} is warming up".format(self.name))

As you can see we have defined the _warmup() method and then we call the private method inside the run() method.

Nothing changes in the way you call run() on the instance we have created before:

jack = Person("Jack")
jack.run()

[output]
Jack is warming up
Jack is running

Now, let’s see what happens if we try to call the _warmup() method directly on the Person instance.

jack._warmup()

[output]
Jack is warming up

The method works!

Why Can You Call a Python Private Method Outside of a Class?

Wait one minute, why does the last Python code example work if we said that _warmup is a private method?!?

That’s because…

Using a single underscore to indicate the name of a Python private method in a class is just a naming convention between developers and it’s not enforced by the Python interpreter.

In other words by prefixing the name of your Python method with a single underscore you are telling other developers (and yourself in case of future code changes) that the method should only be called within the class.

If you don’t follow this principle you do it at your own risk.

In the future, the developer of a class could decide to change a private method without worrying about backward compatibility considering that the private method wasn’t supposed to be called outside the class.

A private method defined in a Python class should not be called on an instance of that class. It should only be called inside the class itself.

What Does Double Underscore Mean At the Beginning of a Method Name in Python?

In Python, it’s also possible to prefix the name of a method with a double underscore instead of a single underscore.

What does it mean exactly?

Update the method _warmup() from the previous example and add another underscore at the beginning of the method name: __warmup().

def run(self):
    self.__warmup()
    print("{} is running".format(self.name))

def __warmup(self):
    print("{} is warming up".format(self.name))

The run() method behaves in the same way when called on the instance:

jack = Person("Jack")
jack.run()

[output]
Jack is warming up
Jack is running

And what happens if we call the method __warmup() on the Person instance?

jack.__warmup()

[output]
Traceback (most recent call last):
  File "private.py", line 45, in <module>
    jack.__warmup()
AttributeError: 'Person' object has no attribute '__warmup'

The Python interpreter throws an exception and tells us that this Person object has no attribute __warmup.

This error message could be misleading considering that this method is present in the class but the Python interpreter is “hiding” it by using something called name mangling.

The purpose of name mangling is to avoid collisions with method names when inheriting a class.

In the next section, we will see what name mangling exactly does.

What is Name Mangling in Python?

In the previous section, we have seen what happens when you prefix method names with two underscores.

But…

…is the Python interpreter hiding these methods completely?

To answer this question we will use the dir() function to see attributes and methods available in our Person instance.

print(dir(jack))

[output]
['_Person__warmup', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name', 'run']

Interestingly, we don’t see __warmup in the list of available methods but we see _Person__warmup.

Could it be our double underscore method?

Let’s try to call it on the instance:

jack = Person("Jack")
jack._Person__warmup()

[output]
Jack is warming up

It worked!

So it looks like our name mangled method is not completely hidden considering that we can access it by adding an underscore and the class name before the method name.

_{class-name}__{name-mangled-method}

In Python, you can access a method whose name starts with double underscores (and doesn’t end with underscores) from an instance of a class. You can do that by adding an underscore and the name of the class before the name of the method. This is called name mangling.

Name Mangling and Inheritance in Python

Let’s learn more about name mangling in relation to Python class inheritance.

Define a class called Runner that inherits the base class Person.

class Runner(Person):
    def __init__(self, name, fitness_level):
        super().__init__(name)
        self.fitness_level = fitness_level

Then execute the public method run() of the parent class on a Runner instance.

kate = Runner("Kate", "high")
kate.run()

[output]
Kate is warming up
Kate is running

This works fine, the child class has inherited the public method run().

And what happens if we try to call the name mangled method?

kate.__warmup()

[output]
Traceback (most recent call last):
  File "private.py", line 19, in <module>
    kate.__warmup()
AttributeError: 'Runner' object has no attribute '__warmup'

We get the expected error due to name mangling.

Notice that we can still call it by using the name of the parent class as we have seen in the previous section:

kate._Person__warmup()

[output]
Kate is warming up

Below you can see the output of the dir() function for the child class.

['_Person__warmup', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fitness_level', 'name', 'run']

Defining a Name Mangled Method in a Child Class

What happens if we define the same name mangled method in our child class?

Let’s find out!

Override the public method run() and the “hidden” method __warmup() in the Runner class.

class Runner(Person):
    def __init__(self, name, fitness_level):
        super().__init__(name)
        self.fitness_level = fitness_level

    def run(self):
        self.__warmup()
        print("{} has started a race".format(self.name))

    def __warmup(self):
        print("{} is warming up before a race".format(self.name))

It’s time for a run!

kate = Runner("Kate", "high")
kate.run()

[output]
Kate is warming up before a race
Kate has started a race

So, both methods in the child class are executed.

One thing I’m curious about it’s how the Python interpreter represents the new name mangled method in the child object considering that for the parent object it was using an underscore followed by the class name.

print(dir(kate))

[output]
['_Person__warmup', '_Runner__warmup', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fitness_level', 'name', 'run']

You can see that the child object has now two mangled methods:

  • _Person_warmup
  • _Runner__warmup

This shows how name mangling prevents collisions with method names when you inherit a class.

Are There Private Attributes in Python?

Given a Python class can we define private attributes in a similar way to what we have done with private methods?

Let’s take the Person class and add to it a private attribute, for example, the email address.

To indicate that a class attribute is private prefix its name with one underscore. This is a naming convention to tell developers that an attribute is private but the Python interpreter does not enforce this convention.

class Person:
    def __init__(self, name, email):
        self.name = name
        self._email = email

Now create a Person instance and try to access both attributes.

>>> mark = Person("Mark", "mark@domain.com")
>>> print(mark.name)
Mark
>>> print(mark._email)
mark@domain.com

From the instance we can access the public and private attributes.

This shows that in the same way we have seen with private methods also private attributes are accessible from outside a Python class.

Once again…

…the single underscore is just a naming convention that tells developers not to directly access or modify those specific class attributes.

To access and modify private attributes implement public class methods that do it. You can then call these public methods from your class instances.

The whole point of private attributes and methods is that you shouldn’t use them directly.

Name Mangling Applied to Class Attributes

In this last section, we will see how name mangling works on class attributes.

Prefix the name of the _email attribute with another underscore. The attribute becomes __email.

class Person:
    def __init__(self, name, email):
        self.name = name
        self.__email = email

Then create a Person instance and try to access both attributes.

>>> mark = Person("Mark", "mark@domain.com")
>>> print(mark.name)
Mark
>>> print(mark.__email)
Traceback (most recent call last):
  File "private.py", line 30, in <module>
    print(mark.__email)
AttributeError: 'Person' object has no attribute '__email'

In the same way we have seen before for name mangled methods, we cannot access a name mangled attribute directly.

But we can access it using the following syntax:

_{class-name}__{attribute-name}

Let’s test it…

print(mark._Person__email)

[output]
mark@domain.com

Conclusion

In this tutorial, we have seen that to create a Python private method or private attribute in a class you have to prefix the name of the method or attribute with a single underscore.

The Python interpreter does not enforce this convention when working with classes, it’s just a naming convention.

Using private methods or private attributes allows you to show other developers which methods or attributes are “for internal use only” in a Python class.

If you want to read more about this convention you can look at PEP 8.

Related article: continue building your Python object-oriented knowledge with a Codefather tutorial about Python class inheritance.

Leave a Comment