I'm doing a class that will be the basis for another ones and i want to prohibit some methods to be override in some situations and I just don't know how to do it.
-
You can use "inner" methods that are not visible from the outside - beside that and the (weak) possibility of name mangling (which can be circumvented) you can't with python. [what-is-monkey-patching](https://stackoverflow.com/questions/5626193/what-is-monkey-patching) – Patrick Artner Oct 29 '21 at 09:32
3 Answers
I'm going to show you three approaches from "not so bad" to the cleanest (in my opinion).
1. class decorator
One way of doing this is using "class decorator" to compare methods of the class itself and its parent:
from inspect import isfunction
def should_not_override_parents_method(cls):
parents_methods = set(k for k, v in cls.__base__.__dict__.items() if isfunction(v))
class_methods = set(k for k, v in cls.__dict__.items() if isfunction(v))
intersection = parents_methods & class_methods
if intersection:
raise Exception(
f"class {cls.__name__} should not implement parents method: "
f"'{', '.join(intersection)}'"
)
return cls
class A:
def fn_1(self):
print("A : inside fn_1")
@should_not_override_parents_method
class B(A):
def fn_1(self):
print("B : inside fn_1")
def fn_2(self):
print("B : inside fn_2")
2. __init_subclass__
from inspect import isfunction
class A:
def __init_subclass__(cls, **kwargs):
parents_methods = set(
k for k, v in cls.__base__.__dict__.items() if isfunction(v)
)
class_methods = set(k for k, v in cls.__dict__.items() if isfunction(v))
intersection = parents_methods & class_methods
if intersection:
raise Exception(
f"class {cls.__name__} should not implement parents method: "
f"'{', '.join(intersection)}'"
)
def fn_1(self):
print("A : inside fn_1")
class B(A):
def fn_1(self):
print("B : inside fn_1")
def fn_2(self):
print("B : inside fn_2")
Note: These are only gonna prevent subclasses from overriding parent's method in creation phase. This means after the class has been created, you can dynamically add those methods to them. To also prevent that you can create a custom metaclass and override __setattr__
method as well:
3. Metaclass
from inspect import isfunction
class Prevent(type):
@staticmethod
def _check_methods(parent_dict, child_dict, child_name):
parents_methods = set(k for k, v in parent_dict.items() if isfunction(v))
class_methods = set(k for k, v in child_dict.items() if isfunction(v))
intersection = parents_methods & class_methods
if intersection:
raise Exception(
f"class {child_name} should not implement parents method: "
f"'{', '.join(intersection)}'"
)
def __new__(cls, name, bases, mapping, **kwargs):
if bases:
parent = bases[0]
Prevent._check_methods(parent.__dict__, mapping, name)
class_object = super().__new__(cls, name, bases, mapping)
return class_object
def __setattr__(cls, name, value) -> None:
if name in cls.__base__.__dict__:
raise Exception(
f"class {cls.__name__} should not have parents method: {name}"
)
super().__setattr__(name, value)
class A(metaclass=Prevent):
def fn_1(self):
print("A : inside fn_1")
class B(A):
def fn_1(self):
print("B : inside fn_1")
def fn_2(self):
print("B : inside fn_2")
output:
Traceback (most recent call last):
File "<>", line 36, in <module>
class B(A):
File "<>", line 19, in __new__
Prevent._check_methods(parent.__dict__, mapping, name)
File "<>", line 11, in _check_methods
raise Exception(
Exception: class B should not implement parents method: 'fn_1'
Now it's worth mentioning that since Python 3.8, there is decorator called @typing.final
which hints you that you shouldn't override this method in the child class. Of course there is no restriction by doing so at runtime.
from typing import final
class A:
@final
def fn_1(self):
print("A : inside fn_1")
class B(A):
def fn_1(self):
print("B : inside fn_1")
def fn_2(self):
print("B : inside fn_2")

- 13,077
- 10
- 22
- 49
In python functions are just members of a class. You can replace them (What is monkey patching) to do somehting completely different.
So even code that is NOT a subclass can substitute a classes function to do different things.
You can name-mangle functions - but that is also circumventable - and they also can be monkey-patched:
class p:
def __init__(self):
pass
def __secret(self):
print("secret called")
def __also_secret(self):
print("also_secret called")
def not_secret(self):
def inner_method():
print("inner called")
inner_method()
class r(p):
def __secret(self): # override existing function
print("overwritten")
Demo:
a = p()
b = r()
# get all the respective names inside the class instance
c = [n for n in dir(a) if n.startswith("_p__")]
d = [n for n in dir(b) if n.startswith("_r__")]
# call the hidden ones and monkey patch then call again
for fn in c:
q = getattr(a, fn)
q() # original executed although "hidden"
q = lambda: print("Monkey patched " + fn)
q() # replaced executed
# call the hidden ones and monkey patch then call again
for fn in d:
q = getattr(b, fn)
# original executed although "hidden"
q = lambda: print("Monkey patched " + fn)
q() # replaced executed
# call public function
a.not_secret()
try:
# a.inner_method() # does not work
a.not_secret.inner_method() # does also not work as is it scoped inside
except AttributeError as e:
print(e)
a.not_secret = lambda: print("Monkey patched")
a.not_secret()
Output:
also_secret called # name mangled called
Monkey patched _p__also_secret # patched of name mangled called
secret called # name mangled called
Monkey patched _p__secret # patched of name mangled called
Monkey patched _r__secret # overwritten by subclass one called
inner called # called the public that calls inner
'function' object has no attribute 'inner_method' # cannot get inner directly
Monkey patched
If you want this feature you need to use a different language - not python.

- 50,409
- 9
- 43
- 69