Search code examples
pythonoopcode-analysispylintprivate-members

Accessing mangled member of another instance


I use this logic to maintain a directed tree of Object instances :

class Object:
    def __init__(self, *parents: 'Object'):
        self.__parents = list(parents)

    @property
    def __ascendants(self):
        for parent in self.__parents:
            yield from parent.__ascendants
            yield parent

This code runs fine, but PyLint is complaining about __ascendants being a protected member of parent, which is, to PyLint, a client class.

In the case of a protected, non-mangled, member, that would be fine : I should not access such members as they could be overriden by an Object subclass.

But in this case, as the attributes are mangled, it's not possible for a subclass to override them, which is why I allow myself to use them even on external objects (provided to the constructor).

TLDR ; I'm looking for a way to make PyLint accept accessing mangled attributes of a client subclass, without having to resort to #pylint: disable=protected-access each time, or disabling the warning globally.

It looks like I can use the astng callback to register a MANAGER, and transform a module so that PyLint can use additional information. However, I was only able to add stub members (so that dynamically added members can be used without warnings), and I'm not really sure that I can solve my problem this way.

I also tried to add assert isinstance(parent, Object), but it won't help.

EDIT :

I was able to write the code so that PyLInt doesn't raise protected-access, but only to raise bad-staticmethod-argument. I don't use other staticmethod s in this particular class, so maybe this can be an acceptable answer to this problem :

class Object:
    def __init__(self, *parents: 'Object'):
        self.__parents = list(parents)

    @staticmethod
    def __get_ascendants(self: 'Object'):
        for parent in self.__parents:
            yield from self.__get_ascendants(parent)
            yield parent

EDIT 2 : (inspired by @shx2)

Using a lambda with the correct argument name also fools Pylint:

class Object:
    def __init__(self, *parents: 'Object'):
        self.__parents = list(parents)

    @property
    def __ascendants(self):
        get_ascendants = lambda self: self.__ascendants

        for parent in self.__parents:
            yield from get_ascendants(parent)
            yield parent

EDIT 3 : Because names do not leak out of generator expressions (or list omprehensions), it can also be written this way :

from itertools import chain

class Object:
    def __init__(self, *parents: 'Object'):
        self.__parents = list(parents)

    @property
    def __ascendants(self):
        return chain(*(
           chain(self.__ascendants, (self, ))
           for self in self.__parents
        ))

Solution

  • I'm looking for a way to make PyLint accept accessing mangled attributes of a client subclass

    There are ways to fool pylint.

    One way is to disguise parent as self:

    @property
    def __ascendants(self):
        for parent in self.__parents:
            self = parent
            yield from self.__ascendants
            yield self
    

    Another is accessing the attribute indirectly, using getattr. Instead of:

    yield from parent.__ascendants
    

    do:

    yield from getattr(parent, '__ascendants')