While c++ and java has authorization system for accessing class entities such as methods and member variables, Python does not have one. In this article, we will see how we can we prevent accidents by creating authorization system which allows to restrict the creation of objects in python.

The Problem

Suppose we want to model a simple world – A Manager and Worker. The Manager has two rules – he hire and fire a Worker. The following restriction should be enforced – Only the Manager can create a Worker object while he hire someone.

We start to code this simple world:

Sample #1
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

We write a simple test for our code:

Sample #1 - test
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 which use our world can create the class Worker.

Sample #1 - test output
$ python test.py
I can create a worker : Worker <1>

Restrict the Creation of Objects – Step 1

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

We will create a simple authorization system. Please note that the main purpose of this authorization system is to avoid accidents – If the programmer wants to bypass this authorization system, he will be able to do it with a little effort.

First, we will use pseudo private variables which is based on the good will of programmer to follow the convection and it is designed mostly to avoid accidents. The current mechanism is name mangling. Any identifier of the from __BB in a class is textually replaced with _classname__BB.

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:

Sample #2 - test
...
print( "I can create a worker : {0}".format(str(world.Manager.__Worker('1')) ) )

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

Sample #2 - test 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:

Sample #2 - test #2
import world
print( "I can create a worker : {0}".format(str(world.Manager._Manager__Worker('1')) ) )
Sample #2 - test #2 output
$ python test2.py
I can create a worker : Worker <1>

Restrict the Creation of Objects - Step 2

Now only methods of Manager can construct the __Worker. However, the restriction was that __Worker can be created only in the hire method.

Find the Caller of a Function

To enforce this we will use the following function which return the caller of the function which it invoked in:

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

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

Sample #3 - _find_callee example
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:

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 fuction named hire can construct __Worker as the test2.py shows:

Sample #3 - test
import world
print( "I can create a worker : {0}".format(str(world.Manager._Manager__Worker('1')) ) )
Sample #3 - test 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.

Summery

Although, the authorization system can be easily skipped, It valuable tool for the programmers who use our module to avoid accidents and using our inner implementation details.