Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

SignUp Now!
  • Guest, before posting your code please take these rules into consideration:
    • It is required to use our BBCode feature to display your code. While within the editor click < / > or >_ and place your code within the BB Code prompt. This helps others with finding a solution by making it easier to read and easier to copy.
    • You can also use markdown to share your code. When using markdown your code will be automatically converted to BBCode. For help with markdown check out the markdown guide.
    • Don't share a wall of code. All we want is the problem area, the code related to your issue.


    To learn more about how to use our BBCode feature, please click here.

    Thank you, Code Forum.

Python Encapsulation in Python

skifli

Backend Developer
I recently learned about encapsulation in Python, and how you could specify private variables in classes that can't be accessed outside of the class (by prefacing the variable with '__'). However, this actually only mangles the name of the variable to '_{CLASS_NAME}{variable_name_with_two_leading_underscores}'. This means that you can still access the variables out of the class if you were determined enough. I decided to try and find a way to block this by writing a custom class that can prevent this. The code below is my attempt at this, and I would appreciate any feedback on it, as well as if someone managed to find a way to get round it.

To make it work, your class has to inherit from the 'Protected' class. This function overrides the default set / get attribute functions, and also uses a custom dictionary class called 'ProtectedDict', since someone could technically edit the dictionary through 'my_class.__dict__["name"] John'. You also have to add a decorator called 'unlock' to all functions in the class except for '__init__', which unlocks the protected variables. To prevent someone from replicating the code of the 'unlock' function, the 'Protected' class checks if the calling function is the right one when 'allow_private' gets set to 'True'.

If you want me to explain anything, feel free to ask since I haven't commented the code very well. I have also included a dummy class called 'Person' for you to see how it works in action.

Python:
from inspect import currentframe


class ProtectedDict(dict):
    def __getitem__(self, key) -> any:
        if (
            "allow_private" not in self.__dict__
            and "initialized" in self.__dict__
            and key.startswith("__")
        ):
            raise AttributeError(f"Class has no attribute '{key}'")

        return super().__getitem__(key)

    def __setitem__(self, key, value) -> None:
        if (
            "allow_private" not in self.__dict__
            and "initialized" in self.__dict__
            and key.startswith("__")
        ):
            raise AttributeError(f"Class has no attribute '{key}'")

        super().__setitem__(key, value)

    def __setattr__(self, key, value) -> None:
        if currentframe().f_back.f_code.co_name in ["__init__", "__inner__"]:
            super().__setattr__(key, value)
        else:
            exec(f"super().__getitem__('variables').{key} = value")


class Protected:
    def __init__(self, **kwargs) -> None:
        self.variables = ProtectedDict()

        for key in kwargs:
            self.variables[key] = kwargs[key]

        self.variables.initialized = True

    def __setattr__(self, key, value) -> None:
        if currentframe().f_back.f_code.co_name == "__init__":
            super().__setattr__(key, value)
        else:
            self.variables[key[key.find("__") :]] = value

    def __getattr__(self, name) -> any:
        return self.variables.__getitem__(name[name.find("__") :])


def unlock(func):
    def __inner__(*args, **kwargs) -> any:
        args[0].variables.allow_private = True
        code = func(*args, **kwargs)
        del args[0].variables.allow_private
        return code

    return __inner__

# END PROTECTED CLASS IMPLEMENTATION

class Person(Protected):
    def __init__(self, __name: str, age: int) -> None:
        super().__init__(__name=__name, age=age)
        print(f"Initialized {__name} as {age} years old.")

    @unlock
    def update_name(self, name: str) -> None:
        print(f"Changing name from {self.__name} to {name} in the class.")
        self.__name = name
        print("Changed the name successfully in the class.")


my_person = Person("John", 20)
my_person.update_name("Jane")

print(f"Changing name from {my_person.__name} to John outside of the class.") # This should error out, saying the '__name' attribute does not exist.
 
Back
Top Bottom