Home > database >  How to raise an error if child class override parent's method in python?
How to raise an error if child class override parent's method in python?

Time:10-30

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.

CodePudding user response:

You are looking for the super() function

CodePudding user response:

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.

CodePudding user response:

One way of doing this is using "class decorator" to compare methods of the class itself and it's parent. This can be done using __init_subclass__ as well. I will show you both:


Class decorator:

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))
    diff = parents_methods & class_methods

    if diff:
        raise Exception(f"class {cls.__name__} should not implement parents method: "
                        f"'{', '.join(diff)}'")
    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")

output:

Traceback (most recent call last):
  File "<>", line 21, in <module>
    class B(A):
  File "<>", line 10, in should_not_override_parents_method
    raise Exception(f"class {cls.__name__} should not implement parents method: "
Exception: class B should not implement parents method: 'fn_1'

__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))
        diff = parents_methods & class_methods

        if diff:
            raise Exception(f"class {cls.__name__} should not implement parents method: "
                            f"'{', '.join(diff)}'")

    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")
  • Related