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:
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.