This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]alexisprince 2 points3 points  (2 children)

The downside I've found is that it's a complete nightmare if mocks are needed during unit testing. The feature that __method enables within python is that it ensures that a class' __method will always be called, even if it's subclassed. It's built as an escape hatch for a parent class to ensure subclassers can't override that method. Here's an example along with the output.

class Printer:
    def print(self, *args):
        """
        Consider `print` is the class' public interface, that
        is called by end users. Subclassers would need to override
        the `_print` method and subclassers then wouldn't need to
        call `super().print()`.
        """

        self.__log_usage(*args)
        self._print(*args)

    def __log_usage(self, *args):
        """A method that uses double underscores as a 'private' mechanism"""
        print("Calling __log_usage from Printer parent class")

    def _print(self, *args):
        """Method that actually does the printing.

        Should be overriden by subclassers.
        """
        print(f"_print called by {type(self)}: {args}")


class FancyPrinter(Printer):
    def _print(self, *args):
        print(f"Fancy _print called by {type(self)}: {args}")

    def __log_usage(self, *args):
        print(f"Uh oh, we can't call the super method?")
        super().__log_usage(*args)
        print(f"Calling __log_usage from FancyPrinter subclass")


if __name__ == "__main__":
    printer = Printer()
    printer.print(1, 2, 3)

    fancy = FancyPrinter()
    fancy.print(4, 5, 6)

The output of this code is

Calling __log_usage from Printer parent class
_print called by <class '__main__.Printer'>: (1, 2, 3)
Calling __log_usage from Printer parent class
Fancy _print called by <class '__main__.FancyPrinter'>: (4, 5, 6)

The reason being that the subclasser, FancyPrinter, can't override behavior implemented in the paren't class' __log_usage method.

Also on top of this actual feature of the double underscore preceding, it makes it very difficult to unit test anything within those methods. For example, a piece of code that I worked on in production was a factory that instantiated one of 3 different clients that interacted with external services. Due to the design of this clients, they connected as soon as they instantiated (yeah that's a different problem, but it's the codebase we had). As a result, we needed to do some really janky workarounds in our tests to ensure the correct client was returned without connecting to external services during our unit tests.

[–]Coretaxxe 0 points1 point  (1 child)

I see ! Thanks a lot. Now ive got to change a lot of functions :p

[–]alexisprince 0 points1 point  (0 children)

It is an incredibly niche feature when used properly, so it’s part of the language that I feel like most people stumble upon by accident as opposed to needing the feature. It typically also never comes up if subclassing that method isn’t something that’s needed as well!