I have the following base class.
class BaseWithMethod:
def __init__(self, prop=None):
self.prop = prop
def evil_method(self):
print(f"%@#&ç? {self.prop}")
I want to create a wrapper class ReadonlyWrapperSelectedMethods
that shows the same functionality as the base class but does not allow certain methods (evil_method
in this example) to be called. Further, wrapped instances should be read-only, as discussed in my other SO question here. That means that calls to __setattr__
should raise an error once the instance is initialized. The behavior is demonstrated in the following code:
# Instantiate the wrapper class
readonly_instance = ReadonlyWrapperSelectedMethods()
# I can access properties
prop = readonly_instance.prop
# This should raise a PermissionError
readonly_instance.prop = 23
# This should also raise a PermissionError
readonly_instance.evil_method()
Is there a way to implement this behavior without modifying the base class? See below how it can be done when the base class may be changed.
So far I have tried the following. I added an attribute _initialized
to the base class and set it to True
at the end of __init__
:
class BaseWithMethodModified:
_initialized = False
def __init__(self, prop=None):
self.prop = prop
self._initialized = True
def evil_method(self):
print(f"%@#&ç? {self.prop}")
In this case the following wrapper class should do the job. It overrides the __getattribute__
method and delegates calls to methods that are allowed to the super class.
class ReadonlyWrapperSelectedMethods(BaseWithMethodModified):
"""Read-only wrapper class."""
def __getattribute__(self, name: str):
if "evil" in name:
raise PermissionError()
else:
return super().__getattribute__(name)
def __setattr__(self, key, value) -> None:
if self.__getattribute__("_initialized"):
raise PermissionError()
else:
super().__setattr__(key, value)
The issue with this attempt is that I do not want to modify the base class and if the attribute _initialized
is defined in the wrapper class, it cannot be accessed since all attribute accesses are delegated to the base class through __getattribute__
. Maybe this can be circumvented in some way?
You could simply override the __init__
method:
class ReadonlyWrapperSelectedMethods(BaseWithMethod):
"""Read-only wrapper class."""
def __init__(self, prop=None):
super().__init__(prop)
self._initialized = True
def __getattribute__(self, name: str):
if "evil" in name:
raise PermissionError()
else:
return super().__getattribute__(name)
def __setattr__(self, key, value) -> None:
if hasattr(self, "_initialized"):
raise PermissionError()
else:
super().__setattr__(key, value)
After __init__
returns, the object is readonly:
>>> readonly_instance = ReadonlyWrapperSelectedMethods()
>>> vars(readonly_instance)
{'prop': None, '_initialized': True}
>>> prop = readonly_instance.prop
>>> readonly_instance.prop = 23
Traceback (most recent call last):
File "<pyshell#126>", line 1, in <module>
readonly_instance.prop = 23
File "<pyshell#121>", line 16, in __setattr__
raise PermissionError()
PermissionError
>>> readonly_instance.evil_method()
Traceback (most recent call last):
File "<pyshell#127>", line 1, in <module>
readonly_instance.evil_method()
File "<pyshell#121>", line 10, in __getattribute__
raise PermissionError()
PermissionError