I am a fan of the engineering design, including class members access modifiers and interfaces, but Python does not have them from the box. I have created own ones. The reference to the library on the Github is presented at the end of the article.

Class members access modifiers

They restrict access to the object’s methods and parent class methods from the child class in case of inheritance. When you call the method under the private access modifier, you get the corresponding error message. You cannot use details of the realization (as you shouldn’t get access to your car’s engine) of the class or change its data (e.g. counting variables), you can only use public functionality.

A private method can be only called inside the particular class, a protectedmethod inside the particular class and by its child classes.

How declare private and protected methods in Python

In Python, «privacy» depends on «consenting adults» levels of agreement. A single leading underscore means you’re not supposed to get access to it from the outside. Two leading underscores carry the message even more seriously. But, in the end, it still depends on social convention and consensus.

class Car:
	

	    def _start_engine(self):
	        return "Engine's sound."
	

	    def run(self):
	        return self._start_engine()
	

	

	if __name__ == '__main__':
	    car = Car()
	

	    assert "Engine's sound." == car.run()
	    assert "Engine's sound." == car._start_engine()

The following cons are there:

  • Imaging method **_start_engine **modifies any class variables or save any state. If you call this method, it could break the class behavior.
  • If you need to call protected method anyway, you should really spend the time to research — «will I break something in my another code by accessing it?».
  • In most cases, you use the class methods from libraries. A class provides public functionality which relies on private methods. Private methods could be changed (refactored, divided by separated code, etc.), without changing the public functionality. Author of the library still offers the promised functionality, but what about you?
  • Imagine your teammate wrote the class with public and private functionality. Another teammate uses a private method outside the class. If you refactor this class, you should pay attention on the class and the code which uses its private functionality. Instead of one place you get two.
  • And you should track your team members do not access private and protected methods on code review. And spend time for it.

How you declare protected methods using the library

from accessify import protected
	

	

	class Car:
	

	    @protected
	    def start_engine(self):
	        return "Engine's sound."
	

	    def run(self):
	        return self.start_engine()
	

	

	if __name__ == '__main__':
	    car = Car()
	

	    assert "Engine's sound." == car.run()
	

	    car.start_engine()

When you call the method **start_engine, **you get an exception — method is inaccessible due to its protection level.

Traceback (most recent call last):
	  File "examples/access/private.py", line 24, in <module>
	    car.start_engine()
	  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/main.py", line 92, in private_wrapper
	    class_name=instance_class.__name__, method_name=method.__name__,
	accessify.errors.InaccessibleDueToItsProtectionLevelException: Car.start_engine() is 
	  inaccessible due to its protection level

So, using the library:

  • You do not use ugly underscores.
  • Instead, you have a native Python way — decorators.
  • Move responsibility of checking the accessing to the methods from human to machine.

How does it work

  1. Decorators **private **and protected are the top placed decorators. They are executed before the method is executed.

  1. Using built-in Python library called inspect, decorator fetches the current frame—inspect.currentframe(). Current frame, as an object, has the following attributes: a namespace and a reference to the previous frame (a frame that calls current frame).

Super simple illustration

  1. Then decorator use **inspect.currentframe().f_back **to know if the frame that called the current frame is located in the class body. Looking for attribute self in the namespace. If it is presented there, the decorator was called from the body of the class. If it isn’t presented, inaccessible due to its protection level exception is raised.

Interfaces

Interface—it is a set of the methods signatures (name, incoming arguments). Interfaces could be implemented by classes. If a class implements an interface, a class is required to implement its methods signatures completely. It allows to declaring high-level requirements for application without a detailed description of the implementation. Below the interface of human is presented—human eats food and goes somewhere.

class HumanInterface:
	

	    @staticmethod
	    def eat(food, *args, allergy=None, **kwargs):
	        pass
	        
	    def walk(self, destination):
	        pass

How you declare interfaces using the library

Methods

If a class implements an interface, a class is required to implement allinterface’s** methods**. In the example below, interface «HumanInterface» contains the method «eat», class «Human» implements the interface, but does not implement the method «eat».

from accessify import implements
	

	

	class HumanInterface:
	

	    @staticmethod
	    def eat(food, *args, allergy=None, **kwargs):
	        pass
	

	

	if __name__ == '__main__':
	

	    @implements(HumanInterface)
	    class Human:
	

	        pass

The script will be finished with the following exception:

Traceback (most recent call last): File "examples/interfaces/single.py", line 18, in <module> @implements(HumanInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string,accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanInterface.eat(food, args, allergy, kwargs)

Arguments

If a class implements an interface, a class is required to implement allinterface’s methods incoming arguments. In the example below, interface «HumanInterface» contains the method «eat» that accept 4 arguments, class «Human» implements the interface, implements the method «eat», but only with 1 argument.

from accessify import implements
	

	

	class HumanInterface:
	

	    @staticmethod
	    def eat(food, *args, allergy=None, **kwargs):
	        pass
	

	

	if __name__ == '__main__':
	

	    @implements(HumanInterface)
	    class Human:
	

	        @staticmethod
	        def eat(food):
	            pass

The script will be finished with the following exception:

Traceback (most recent call last):
	  File "examples/interfaces/single_arguments.py", line 16, in <module>
	    @implements(HumanInterface)
	  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 87, in decorator
	    interface_method_arguments=interface_method.arguments_as_string,
	accessify.errors.InterfaceMemberHasNotBeenImplementedWithMismatchedArgumentsException: 
	  class Human implements interface member HumanInterface.eat(food, args, allergy, kwargs) 
	  with mismatched arguments

Access modifiers

If a class implements an interface, a class is required to implement all the interface’s methods, their incoming arguments, and access modifiers. In the example below, interface «HumanInterface» contains the private method «eat», class «Human» implements the interface, but implement the method «eat» without private access modifier.

from accessify import implements, private
	

	

	class HumanInterface:
	

	    @private
	    @staticmethod
	    def eat(food, *args, allergy=None, **kwargs):
	        pass
	

	

	if __name__ == '__main__':
	

	    @implements(HumanInterface)
	    class Human:
	

	        @staticmethod
	        def eat(food, *args, allergy=None, **kwargs):
	            pass

The script will be finished with the following exception:

Traceback (most recent call last):
	  File "examples/interfaces/single_access.py", line 18, in <module>
	    @implements(HumanInterface)
	  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 77, in decorator
	    interface_method_name=interface_method.name,
	accessify.errors.ImplementedInterfaceMemberHasIncorrectAccessModifierException: 
	  Human.eat(food, args, allergy, kwargs) mismatches 
	  HumanInterface.eat() member access modifier.

Multiple interfaces

A class could implement multiple interfaces. If a class implements multiple interfaces, a class is required to implement all interface’s methods, their incoming arguments, and access modifiers. In the example below, interface «Human» implements the method «eat» from the interface «HumanBasicsInterface», but does not implement the method «love» from the interface «HumanSoulInterface».

from accessify import implements
	

	

	class HumanSoulInterface:
	

	    def love(self, who, *args, **kwargs):
	        pass
	

	

	class HumanBasicsInterface:
	

	    @staticmethod
	    def eat(food, *args, allergy=None, **kwargs):
	        pass
	

	

	if __name__ == '__main__':
	

	    @implements(HumanSoulInterface, HumanBasicsInterface)
	    class Human:
	

	        def love(self, who, *args, **kwargs):
	            pass

The script will be finished with the following exception:

Traceback (most recent call last): File "examples/interfaces/multiple.py", line 19, in <module> @implements(HumanSoulInterface, HumanBasicsInterface) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 66, in decorator interface_method_arguments=interface_method.arguments_as_string,accessify.errors.InterfaceMemberHasNotBeenImplementedException: class Human does not implement interface member HumanBasicsInterface.eat(food, args, allergy, kwargs)

Declare exceptions required to be thrown

There is a feature when you declare exceptions required to be thrown by the class method. In the example below, the method «love» from the interface «HumanInterface» says that implementation should raise «HumanDoesNotExistError» and «HumanAlreadyInLoveError» errors. But method «love» in the class «Human» does not raise one of them.

from accessify import implements, throws
	

	

	class HumanDoesNotExistError(Exception):
	    pass
	

	

	class HumanAlreadyInLoveError(Exception):
	    pass
	

	

	class HumanInterface:
	

	    @throws(HumanDoesNotExistError, HumanAlreadyInLoveError)
	    def love(self, who, *args, **kwargs):
	        pass
	

	

	if __name__ == '__main__':
	

	    @implements(HumanInterface)
	    class Human:
	

	        def love(self, who, *args, **kwargs):
	

	            if who is None:
	                raise HumanDoesNotExistError('Human whom need to love does not exist')

The script will be finished with the following exception:

Traceback (most recent call last):
	  File "examples/interfaces/throws.py", line 21, in <module>
	    @implements(HumanInterface)
	  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/accessify/interfaces.py", line 103, in decorator
	    class_method_arguments=class_member.arguments_as_string,
	accessify.errors.DeclaredInterfaceExceptionHasNotBeenImplementedException: 
	  Declared exception HumanAlreadyInLoveError by HumanInterface.love() 
	  member has not been implemented by Human.love(self, who, args, kwargs)

So, using the library:

  • You can implement one or multiple interfaces.
  • Interfaces incorporate with access modifiers.
  • You do not need to create interfaces based on the ABC module in Python.
  • When you define an abstract class with the **ABC module, **you will get errors during the calling the class. If you use interfaces, you will get errors during defining a class-implementation.

How does it work

  1. Decorator **implements **using the **inspect.getmembers() fetch **allmembers of the classes and their interfaces.
  2. With **inspect.signature() decorator fetch each **method’s arguments.
  3. Iterate the methods in the loop and compare methods names, arguments, access modifiers, and declare exceptions required to be thrown.

Thank you for your attention! The library on the Github—https://github.com/dmytrostriletskyi/accessify

#python

Private method without underscores and interfaces in Python
3 Likes28.65 GEEK