Search code examples
python-3.xstatic-methods

In Python, is what are the differences between a method outside a class definition, or a method in it using staticmethod?


I have been working a a very dense set of calculations. It all is to support a specific problem I have.

But the nature of the problem is no different than this. Suppose I develop a class called 'Matrix' that has the machinery to implement matrices. Instantiation would presumably take a list of lists, which would be the matrix entries.

Now I want to provide a multiply method. I have two choices. First, I could define a method like so:

class Matrix():
    def __init__(self, entries)
    # do the obvious here
    return

    def determinant(self):
        # again, do the obvious here
        return result_of_calcs

    def multiply(self, b):
        # again do the obvious here
        return

If I do this, the call signature for two matrix objects, a and b, is

a.multiply(b)...

The other choice is a @staticmethod. Then, the definition looks like:

    @staticethod
    def multiply(a,b):
    # do the obvious thing.

Now the call signature is:

z = multiply(a,b)

I am unclear when one is better than the other. The free-standing function is not truly part of the class definition, but who cares? it gets the job done, and because Python allows "reaching into an object" references from outside, it seems able to do everything. In practice they'll (the class and the method) end up in the same module, so they're at least linked there.

On the other hand, my understanding of the @staticmethod approach is that the function is now part of the class definition (it defines one of the methods), but the method gets no "self" passed in. In a way this is nice because the call signature is the much better looking:

z = multiply(a,b)

and the function can access all the instances' methods and attributes.

Is this the right way to view it? Are there strong reasons to do one or the other? In what ways are they not equivalent?


Solution

  • I have done quite a bit of Python programming since answering this question.

    Suppose we have a file named matrix.py, and it has a bunch of code for manipulating matrices. We want to provide a matrix multiply method.

    The two approaches are:

    1. define a free:standing function with the signature multiply(x,y)
    2. make it a method of all matrices: x.multiply(y)

    Matrix multiply is what I will call a dyadic function. In other words, it always takes two arguments.

    The temptation is to use #2, so that a matrix object "carries with it everywhere" the ability to be multiplied. However, the only thing it makes sense to multiply it with is another matrix object. In such cases there are two equally good ways to do that, viz: z=x.multiply(y) or z=y.multiply(x)

    However, a better way to do it is to define a function inside the file that is:

        multiply(x,y)
    

    multiply(), as such, is a function any code using the 'library' expects to have available. It need not be associated with each matrix. And, since the user will be doing an 'import', they will get the multiply method. This is better code.

    What I was wrongly confounding was two things that led me to the method attached to every object instance:

    1. Functions which need to be generally available inside the file that should be exposed outside it; and
    2. Functions which are needed only inside the file.

    multiply() is an example of type 1. Any matrix 'library' ought to likely define matrix multiplication.

    What I was worried about was needing to expose all the 'internal' functions. For example, suppose we want to make externally available matrix add(), multiple() and invert(). Suppose, however, we did not want to make externally available - but needed inside - determinant().

    One way to 'protect' users is to make determinant a function (a def statement) inside the class declaration for matrices. Then it is protected from exposure. However, nothing stops a user of the code from reaching in if they know the internals, by using the method matrix.determinant().

    In the end it comes down to convention, largely. It makes more sense to expose a matrix multiply function which takes two matrices, and is called like multiply(x,y). As for the determinant function, instead of 'wrapping it' in the class, it makes more sense to define it as __determinant(x) at the same level as the class definition for matrices.

    You can never truly protect internal methods by their declaration, it seems. The best you can do is warn users. the "dunder" approach gives warning 'this is not expected to be called outside the code in this file'.