sa2c / python-oop-novice

An introduction to object-oriented programming for Python users in research
https://sa2c.github.io/python-oop-novice/
Other
1 stars 5 forks source link

Mention `@staticmethod` #21

Open chillenzer opened 1 year ago

chillenzer commented 1 year ago

As @classmethod gets a whole section, we could quickly mention @staticmethod? Maybe in a note?

edbennett commented 1 year ago

The initial skeleton for the lesson had "@staticmethod and @classmethod" here, but as I wrote it I realised I didn't have a good motivating example for @staticmethod, so skipped it.

I don't object to adding a callout if we can include a good motivating example, but we should probably be careful to manage the cognitive load/number of new concepts here.

chillenzer commented 1 year ago

Yeah, I'm not good at managing that. Ignoring that is more fun. But back to the point...

Well, @staticmethod more or less acts like a namespace. The best use case is, I'd say, providing or computing some static information about the class. It makes more sense in the realm of class templates where even the static object has some variability in it (see examples from C++ in Grid) but in python, e.g., something like

class Rectangle:
    @staticmethod
    def num_corners():
        return 4
print(Rectangle.num_corners())

This seems a little dump but you could have a situation in which such seemingly static information is still changing over time like, say, you want to do something with the filesystem and provide a static method that returns the number of files without actually going through the whole resource acquisition.

The difference to a free function is that it is parametrised with the class, so you could do something like

for shape in [Rectangle, Circle, Triangle]:
    print(shape.num_corners())

which would be less convenient with free functions.

You can use a static method like a class method if you want to decouple it's implementation details from the class it is called from by design:

class A:
    @classmethod
    def f_class(cls):
        return cls  # or better do something complicating with `cls`

    @staticmethod
    def f_static():
        return A  # or better do something complicating with `A`

class B:
    pass

print(B.f_class())
print(B.f_static())

prints

__main__.B
__main__.A
edbennett commented 1 year ago

I'm afraid neither of those seems particularly motivating to me. I can see how there are contexts that they might be useful in, but I fear trying to wrap a learner's head around that would just generate noise rather than understanding.

How about a callout along the lines of the following?

@staticmethods

If you have a function that doesn't even need to know the class, you can also use the @staticmethod decorator to avoid having either a self or a cls parameter. This could be useful if you have a related function that it makes sense to group together with the class, but that does not directly act on a specific instance.

If you find yourself writing a class that has nothing but @staticmethods, then you should reconsider whether a class is what you need—it may make more sense to use a module instead.

chillenzer commented 1 year ago

Sorry, if that was unclear before: I wouldn't go for more than a callout either way. But I'd say the strongest argument for them is having functions parametrised by a class (and potentially inherited and/or overridden). You could achieve the same interface with a free function taking an additional class parameter but that would be uglier from the outside and a horrible if-statement in the implementation details.

edbennett commented 1 year ago

That's a case that isn't mentioned in the docs (which are admittedly pretty sparse on @staticmethod) nor in any of the StackOverflow threads I found on "why is @staticmethod useful?" (most of which admittedly just focus on what it does, and not where there are possible applications). This does seem to be what the top result from the 2.6M results for searching for @staticmethod on GitHub is doing however. The next couple include "classes that could be modules", and a utility function called by other methods where the attachment to the class is just for grouping.

Edit: Oops, hit the wrong button; that was supposed to just be a comment, not a "mic drop, issue closed" comment.

chillenzer commented 1 year ago

Even the original PEP252 has no motivation other than: "Cool that we can do that now." I've been through the 2001 python-dev mailing lists but got bored at some point and thought I'd actually start working again.

The important question is: Have I convinced you with my motivation?

edbennett commented 1 year ago

I'm convinced it's worth a brief callout; I don't think it's worth going into detail or including any examples of it.