Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protected attributes in classmethod

Python conventions suggest to proceed the names of protected attributes with an underscore. As far as I understand protected attributes should only be used within a given class and its subclasses. Could you then provide me with some intuition on why pylint returns protected-access warnings when I try to use protected attributes inside alternative initializers, e.g.

class Test(object):

    def __init__(self, name):
        self.name = name
        self._count = 0
        self._bound = 1  # max value

    @classmethod
    def specific_state(cls, name, bound):
        test = cls(name)
        test._bound = bound

I do understand that in this specific case (presented in the example above) I work on an instance of an object but it is still inside the class definition thus it seems ok from my point of view. Is pylint a bit too rigorous in this matter or do I misunderstand sth?

like image 325
Szpilona Avatar asked Oct 17 '25 02:10

Szpilona


2 Answers

In my opinion, pylint is too vigorous on this one. I doubt that there would be too much disagreement on this. I can't speak for the pylint developers, but I would guess that this is more of a problem with the type inferencing than necessarily being considered their ideal behavior.


Different people will probably tell you different opinions on referencing members that lead with an underscore. My personal opinion is that

  • If it is in the same module, it's fair game
    • however, you should still strive to limit this as much as possible to avoid tying class/function implementations together where possible.
  • If the member belongs to a different module, it is no longer fair game and should be treated as an implementation detail.
    • tying the implementations together here is inherently dangerous as changes to one implementation could easily break the other code without warning.

So this means that if I have class Foo (with member _member) defined in foo.py and if I make a subclass of Foo (lets call it Bar) in bar.py, I don't believe that Bar should make any explicit reference to the _member attribute. However, if you move Bar into foo.py, then it's Ok. In fact, class Baz (also defined in foo.py) should also be allowed to rely on the internals of Foo -- but it better have a good reason for doing so.

Mostly, I'm not OK with the treatment of underscore prefixed members as "protected" (in the Java sense). I think they are meant to be treated as "implementation details". Protected works a lot better in Java because if you change the implementation (e.g. remove a protected member), the code will fail at compile time and let you know that the protected member your subclass relies on is no longer there. Python has no such safe-guards built in.

Because underscore prefixed names are "implementation details", if it's the same class (like the situation you describe in your question), then it's the same implementation and so (to me) allowing access to those members is a no-brainer. I'd # pylint: disable=protected-access that without a second thought :-).

like image 59
mgilson Avatar answered Oct 18 '25 16:10

mgilson


From pylint.checkers.classes.ClassChecker in the _check_protected_attribute_access method:

'''Given an attribute access node (set or get), check if attribute
access is legitimate. Call _check_first_attr with node before calling
this method. Valid cases are:
* self._attr in a method or cls._attr in a classmethod. Checked by
_check_first_attr.
* Klass._attr inside "Klass" class.
* Klass2._attr inside "Klass" class when Klass2 is a base class of
    Klass.
'''

You can see that your case in not among the mentioned cases. So one could assume that the protected-access warning is legit.

But:

  1. from pylints code itself the exact same case occurred. (e.g. exceptions.py:338):

    # pylint: disable=protected-access
    exc = exc._proxied
    

    So basically they disabled their on checker in that case.

  2. in c++ the following code is valid:

            class X
    {
    private:
        int a;
    protected:
        int b;
    public:
        static void staticMethod(X& x)
        {
            x.a = 1;
            x.b = 1;
        }
    };
    

    So accessing protected/private members in static methods is valid.

I would say that pylint is overly sensitiv in this case. You could use a comment to disable the checker there. The reason that the warning is produced is probably that it is kind of hard for a static checker to implement a more complex behavior.

like image 28
Wombatz Avatar answered Oct 18 '25 15:10

Wombatz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!