[Tutor] best practices for where to set instance member variables

Steven D'Aprano steve at pearwood.info
Sat Sep 4 03:41:58 CEST 2010


On Sat, 4 Sep 2010 05:17:34 am Gregory, Matthew wrote:
> Hi all,
>
> Is there a guideline on where instance member variables should be set
> within a class?


The normal terminology used in Python is "attributes" instead of "member 
variables", and "methods" rather than "member functions".


[...]
> class Foo:
>     def __init__(self, a):
>         self._f1(a)
>     def _f1(self, a):
>         self.a = a

In general, I would avoid that style of coding. Method _f1 operates by 
side-effect. That makes it harder to test, which means you probably 
avoid testing it, which means it probably has bugs. The more 
complicated _f1 is, the more you need to test it, and the less likely 
you are to do so. This is a bad mix!

You should try to limit methods with side-effects to only those that 
*need* to have side-effects, like list.append() and similar.

(Note: functional programming proponents would argue that you *never* 
need to have side-effects, except perhaps in one tiny little corner of 
the language where you handle file I/O. Maybe so, but Python is not and 
never will be a pure FP language, and there's no need to take it to 
such extremes to realise that side-effects are often bad and should be 
avoided as much as possible.)


> -or-
>
> class Foo:
>     def __init__(self, a):
>         self.a = self._f1(a)
>     def _f1(self, a):
>         return a

That's much better. Now you can easily write a test to ensure that _f1 
is correct, without having to worry about side-effects.

However, remember that _f1 is being called from within the 
initialization method. That means that at the point that _f1 is called, 
self is not fully initialized! That makes it hard to reason about the 
state of the instance, and hard to know which attributes exist, and 
which methods are safe to call. These added complications should be 
avoided. Since you can't rely on self, it's best to avoid passing it as 
an argument to the method:

    @staticmethod  # nothing like C++ static methods
    def _f1(a):
        # Method _f1 can't see self or self's class.
        return a

    @classmethod
    def _f1(cls, a):
        # Method _f1 can't see self but does see self's class.
        return a

Instead of hoping that the caller will remember that _f1 can't rely on 
self being fully initialized, you can prohibit it.

The worst case is when _f1 needs access to self. That requires careful 
handling, and is best avoided.


Aside: 

The use of private methods in Python is looked on with mild suspicion. 
It's not so much frowned upon, as considered a sign that the author is 
trying to write Java or C++ instead of Python. My own feeling is, 
anytime I think I want a private method, that's possibly a hint that 
the method is too specific and not general (i.e. useful) enough. But 
I'll assume that you have a good reason for making _f1 a private method 
rather than public. Just remembering that in Python, there's no such 
thing as "private", it's just a naming convention.



> The first method seems to me to 'hide' where the variables get set,

Which is another reason to avoid it.


> but in looking through some of my site-packages, developers seem to
> do this both ways.

Yes, they do. Doesn't mean you have to follow their bad ideas :)


-- 
Steven D'Aprano


More information about the Tutor mailing list