Private methods In Python [2 Best Approaches]

Can we use private methods in Python? While c++ and java have an authorization system for accessing class entities such as methods and member variables, Python does not have an authorization system. In this article, we will see how we can prevent accidents by creating an authorization system that supports private access. We also see how to use this system to restrict the creation of objects in Python.

All Methods Are Public In Python

Suppose we want to model a simple world – A Manager and Worker. The Manager has two rules – he hires and fires a Worker. However, we want to enforce the following restriction – Only the Manager can create a Worker object while he hires someone.

We start to code this simple world:

Sample 1 – In Python All Methods Are Public
 class Worker:
     def __init__(self,name):
         self.name = name
     def __str__(self):
         return 'Worker <{0}>'.format(self.name)

 class Manager:
     def __init__(self,name):
         self.name = name
         self.workers = []
     def __str__(self):
         return 'Manager <{0}> | {1}'.format(
             self.name,
             ','.join( [str(worker) for worker in self.workers] )
         )

     def hire(self, name):
         worker = Worker(name)
         self.workers.append(worker)
         return worker

     def fire(self, name):
         ii = 0
         while ii < len(self.workers):
             if self.workers[ii].name == name:
                 self.workers.pop(ii)
             else:
                 ii += 1
Test for Sample 1
 import world
 manager = world.Manager('A') ; assert str(manager) == "Manager <A> | "
 manager.hire('1')            ; assert str(manager) == "Manager <A> | Worker <1>"
 manager.hire('2')            ; assert str(manager) == "Manager <A> | Worker <1>,Worker <2>"
 manager.fire('1')            ; assert str(manager) == "Manager <A> | Worker <2>"

 print( "I can create a worker : {0}".format(str(world.Worker('1')) ) )

When we run the test, it seems to work as expected. However, the output shows that the programmer who uses our world can create the class Worker. In other words, the __init__ initializer, as any method in python, is public.

Test for Sample 1 – Output
 $ python test.py
 I can create a worker : Worker <1>

Private Methods In Python With Pseudo Private Variables

Python does not have a built-in authorization system for accessing entities. So, how can we control who can construct the Worker object?

We will create a simple authorization system to add private methods to a class. Please note that the primary purpose of this authorization system is to avoid accidents. If the programmer wants to bypass this authorization system, he will do it with a bit of effort.

First, we will use the pseudo private variables convection of python. The pseudo-private variables convection add private variables support in python classes. It was essentially designed to avoid accidents, and like any other convection, it is based on the goodwill of the programmer to follow it. 

The current mechanism of pseudo-private variables is name mangling. Any identifier of the from __BB in a class is textually replaced with _classname__BB. In other words, the method is still public and not private as you can access it via the _classname__BB. However, you can not access it as __BB. Consequently, the convection is called pseudo-private variables.

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.

From pseudo private variables convection

We will define the Worker class in Manager and make it ‘private’ by adding ‘__‘ at the beginning of the name:

Sample 2 – Pseudo Private Variables
 class Manager:
     class __Worker:
         ...

     def __init__(self,name):
         ...
     def __str__(self):
         ...

     def hire(self, name):
         worker = Manager.__Worker(name)
         self.workers.append(worker)
         return worker

     def fire(self, name):
         ...

We will also change the test to reflect the name change:

Test Update For Sample 2
 ...
 print( "I can create a worker : {0}".format(str(world.Manager.__Worker('1')) ) )

Now, when running the test, an exception is raised:

Sample 2 – Output
 $ python test.py
 Traceback (most recent call last):
     File "test.py", line 7, in <module>
     print( "I can create a worker : {0}".format(str(world.Manager.__Worker('1')) ) )
 AttributeError: type object 'Manager' has no attribute '__Worker'

As said, the programmer can still create the class if he wants to and know about the name mangling mechanism, as the following test shows:

Test 2 For Sample 2
 import world
 print( "I can create a worker : {0}".format(str(world.Manager._Manager__Worker('1')) ) )
Test 2 For Sample 2 – Output
 $ python test2.py
I can create a worker : Worker <1>

It is nice solution to private methods in python. Let’s see how can we improve this with checking the function callee.

Private Methods In Python With Checking Function Callee

Now only methods of Manager can construct the __Worker. However, the requirement was to restrict was that Worker can be created only in the hire method.

How can enforce this restriction? First let’s learn how to find the function callee. then we see how finding the caller of the function help us to create the authorization system of python.

Find the Caller of a Function

To find the caller of the function we can use the trackback module to find the frames in the call stack.

Find the Caller of a Function
 import traceback
 import sys

 def _find_callee():
     frames = traceback.extract_stack() # <== D
     return frames[-3].name if len(frames) >= 2 else ""

Find the Caller of a Function In Action

Let’s see why this will work. Suppose we the following code:

Find the Caller of a Function Sample
 def function_0():
     function_1()          # <== B
 def function_1():
     print(_find_callee()) # <== C
 function_0()              # <== A

The following shows the state of the call stack when A,B,C and D statements are executed:

private methods in pythonn - Find the callee
private methods in pythonn – Find the callee

At D – The traceback.extract_stack returns the current traceback from the current stack frame. As we can see, the caller of the function_1 which call _find_callee is located at position 3 from the end of the stack.

Checking the Caller

Now, with this function at hand, we can update the __Worker constructor:

Sample 3 – Checking the callee
 class Manager:
     class __Worker:
         def __init__(self,name):
             if _find_callee() != 'hire':
                 raise BaseException('Authorization error : only Manager.hire can construct me')
             self.name = name
         ...
     ...

Now, only a fumction named hire can construct __Worker as the test2.py shows:

Test for Sample 3
 import world
 print( "I can create a worker : {0}".format(str(world.Manager._Manager__Worker('1')) ) )
Test for Sample #3 – Output
 $ python test2.py
 Traceback (most recent call last):
   File "test2.py", line 2, in <module>
     print( "I can create a worker : {0}".format(str(world.Manager._Manager__Worker('1')) ) )
   File "F:/03/world.py", line 12, in __init__
     raise BaseException('Authorization error : only Manager.hire can construct me')
 BaseException: Authorization error : only Manager.hire can construct me

Note, that the user can read the above code and bypass the our authorization system by calling the construction code from a function named hire.

Adding Private Methods In Python

We see how we can add private methods in python with a custom-made authorization system. In the first approach for adding private methods in python, we leverage the pseudo-private variables convection. The second approach was to check in the private method that the function that called it was allowed to do so.

Although the developer can easily skip our authorization system, It can be a valuable tool for the programmers who use our module to avoid accidents and to use our inner implementation details. What do you think? Share your feedback.

Leave a Reply

Your email address will not be published. Required fields are marked *