Ensure a class always uses its own version of a method rather than the one defined in a subclass?

34 Views Asked by At

I have this code:

class A:
    def __init__(self, vals: list):
        self._vals = vals

    def __len__(self) -> int:
        # some side effects like logging maybe
        return len(self._vals)

    def print_len(self) -> None:
        # some function that uses the len above
        print(len(self))


class B(A):
    def __len__(self) -> int:
        return 0

The issue is, I want print_len to always call A.__len__. I can do this:

class A:
    def __init__(self, vals: list):
        self._vals = vals

    def __len__(self) -> int:
        return len(self._vals)

    def print_len(self) -> None:
        print(A.__len__(self))


class B(A):
    def __len__(self) -> int:
        return 0

But it feels wrong. Basically I want B to lie about __len__ to outside callers, but internally use the correct len specified in A.

So

a = A([1, 2, 3])
print(len(a))  # print 3
a.print_len()  # print 3 - no surprises there

b = B([1, 2, 3])
print(len(b))  # print 0 - overload the __len__
b.print_len()  # want this to be 3 using A's __len__, not 0 using B's __len__

Is there any way to ensure a class always uses its own version of a method rather than a subclass' version? I thought name mangling of dunder methods would help here.

1

There are 1 best solutions below

1
On

I think your approach is a good one. The zen of Python states that "There should be one-- and preferably only one --obvious way to do it." and I think you've found it.

That being said, you can do this via name mangling. You just need to prefix the method with double underscores (don't add them to the end like magic methods). This will create a private method which won't ever be overwritten by subclasses.

I think this might be self-defeating since you're now putting the computation in a different method.

class A:
    def __init__(self, vals: list):
        self._vals = vals

    def __len__(self) -> int:
        return self.__length()

    def __length(self) -> int:
        return len(self._vals)

    def print_len(self) -> None:
        print(self.__length())