pylint icon indicating copy to clipboard operation
pylint copied to clipboard

False positive on member classes accessing private attributes of parent

Open tomkcook opened this issue 3 years ago • 4 comments

Bug description

In the example below, it's completely normal for the Command class to access a private member of the Service class.

class Service:
    class Command:
        def __init__(self, service:'Service'):
            reply = queue.Queue()
            self.service = service
        def execute(self):
            self.service._execute(self.reply)
        def get_reply(self):
            return self.reply.get()
            
    def __init__(self):
        self.commands = queue.Queue()
        self.running = True
        self.worker = threading.Thread(target=self.run)
        self.worker.start()

    def run(self):
        while self.running:
            try:
                command = self.commands.get(timeout=1)
                command.execute()
            except queue.Empty:
                pass

    def _execute(self, reply):
        reply.put("Done")

    def Execute(self):
        command = Service.Command()
        self.commands.put(command)
        return command.get_reply()

Command used

pylint lint.py

Pylint output

************* Module lint
lint.py:11:12: W0212: Access to a protected member _execute of a client class (protected-access)

------------------------------------------------------------------
Your code has been rated at 9.66/10 (previous run: 9.66/10, +0.00)

Expected behavior

It should be okay for a member class to access its parents' private attributes. I can't think of a clearer way to express that the two classes are intimately related.

Pylint version

$ pylint --version
pylint 2.15.6
astroid 2.12.13
Python 3.10.6 (main, Nov  2 2022, 18:53:38) [GCC 11.3.0]

OS / Environment

Ubuntu 22.04

tomkcook avatar Nov 24 '22 15:11 tomkcook

This is a more specific version of #4362 .

tomkcook avatar Nov 24 '22 15:11 tomkcook

Shorter reproducing example:

class Service:
    class Command:
        def __init__(self, service):
            self.reply = []
            self.service = service

        def execute(self):
            # Access to a protected member _execute of a client class (protected-access)
            self.service._execute(self.reply)

    def __init__(self):
        self.commands = []

    def _execute(self, reply):
        reply.append("Done")

    def execute(self):
        command = Service.Command(self)
        self.commands.append(command)

nickdrozd avatar Nov 24 '22 23:11 nickdrozd

Shorter still and more abstract:

class Outer:
    class Inner:
        def protected_access(self, outer):
            print(outer._attr)

    def __init__(self, val):
        self._attr = val
        Outer.Inner().protected_access(self)

nickdrozd avatar Nov 25 '22 00:11 nickdrozd

Personally, I am not convinced that an object of Inner class should automatically have access to private methods of each object of Outer class just because Inner is defined inside Outer. I even tend to avoid accessing private methods of another object of the same class. Ideally, IMO, ._<name> should only follow self.

I have not found any documentation of the "best practices" of accessing private methods of other objects though. I imagine that explicitly allowing exceptions (friend classes) may be useful.

alexeymuranov avatar May 18 '25 15:05 alexeymuranov