Created
November 14, 2019 10:16
-
-
Save bunop/9ef97d74992b384ca5e41a38338f9d07 to your computer and use it in GitHub Desktop.
Python mangling and private methods etude
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# -*- coding: utf-8 -*- | |
""" | |
Created on Thu Nov 14 10:18:35 2019 | |
@author: Paolo Cozzi <[email protected]> | |
An etude of private methods on python and name mangling. For references, a | |
recap of underscore naming convenctions: | |
https://dbader.org/blog/meaning-of-underscores-in-python | |
https://pep8.org/#naming-conventions | |
A reference to Dive Into Python for private methos, that is name mangling, | |
changing function name with a predefined pattern. Those functions are not | |
truly privates, you can access them using the mangling name convection: | |
https://linux.die.net/diveintopython/html/object_oriented_framework/private_functions.html | |
A nice example of a decorator to make a method private in python. More info at: | |
https://medium.com/@vishwa.efor/handy-python-features-e33751ef98a8 | |
""" | |
import inspect | |
from functools import wraps | |
# https://medium.com/@vishwa.efor/handy-python-features-e33751ef98a8 | |
class PrivateFunctionError(Exception): | |
def __init__(self): | |
super().__init__( | |
self, | |
"This is a private function and should not be called by outside") | |
def private_function(function): | |
@wraps(function) | |
def run_if_not_private(*args, **kwargs): | |
caller = inspect.stack()[1][4][0].strip() | |
print("Caller : {}".format(caller)) | |
if ("self." + function.__name__) in caller: | |
return function(*args, **kwargs) | |
else: | |
raise PrivateFunctionError() | |
return run_if_not_private | |
class Parent(object): | |
def public(self): | |
print("I'm a Parent's public method") | |
def __private(self): | |
"""A method that will change is name using the name mangling""" | |
print("I'm a Parent's private method") | |
def __str__(self): | |
return "I'm a Parent's special method" | |
def _internal(self): | |
print("I'm a Parent's internal method") | |
def call_parent_private(self): | |
"""Calling private method""" | |
self.__private() | |
@private_function | |
def a_truly_private_method(self): | |
print("I'm a Parent's decorated private method") | |
@private_function | |
def __a_mangled_private_method(self): | |
print("I'm a Parent's mangled and decorated private method") | |
class Child(Parent): | |
def call_child_private(self): | |
"""Calling private method""" | |
self.__private() | |
if __name__ == "__main__": | |
# define parent | |
parent = Parent() | |
# calling private function through a public method | |
parent.call_parent_private() | |
# calling private function doesn't work, since the name mangling has | |
# renamed this function as _Parent__private | |
try: | |
parent.__private() | |
# 'Parent' object has no attribute '__private' | |
except AttributeError as e: | |
print(e) | |
# however private methods are masked using '_<class>__<function>' | |
# pattern attribute, so you could access private methods using this | |
# name convenction | |
parent._Parent__private() | |
# define child | |
child = Child() | |
# call parent private function through parent public method | |
child.call_parent_private() | |
# calling parent private function through child public method doesn't | |
# work since, child hasn't method __private due to the parent name mangling | |
try: | |
child.call_child_private() | |
# 'Child' object has no attribute '_Child__private' | |
except AttributeError as e: | |
print(e) | |
# however , you can always refer to the parent mangled function | |
child._Parent__private() | |
# now call function decorated with the @private_function | |
# this is a truly private method, however you can't hide this name | |
# from outside class namespace | |
try: | |
parent.a_truly_private_method() | |
# (PrivateFunctionError(...), 'This is a private function and should | |
# not be called by outside') | |
except PrivateFunctionError as e: | |
print(e) | |
# and this private function is inherited from the child class | |
try: | |
child.a_truly_private_method() | |
# (PrivateFunctionError(...), 'This is a private function and should | |
# not be called by outside') | |
except PrivateFunctionError as e: | |
print(e) | |
# what happen if I apply the @private_function decorator to a | |
# mangled fucntion? This method is not named like this | |
# 'Parent' object has no attribute '__a_mangled_private_method' | |
try: | |
parent.__a_mangled_private_method() | |
# 'Parent' object has no attribute '__private' | |
except AttributeError as e: | |
print(e) | |
# and the real name is not accessible from the outside | |
try: | |
parent._Parent__a_mangled_private_method() | |
# (PrivateFunctionError(...), 'This is a private function and should | |
# not be called by outside') | |
# (PrivateFunctionError(...), 'This is a private function and should | |
# not be called by outside') | |
except PrivateFunctionError as e: | |
print(e) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment