Question about the @staticmethod decorator
Cameron Simpson
cs at cskk.id.au
Sun Mar 17 17:46:12 EDT 2019
On 17Mar2019 20:24, Paul Moore <p.f.moore at gmail.com> wrote:
>On Sun, 17 Mar 2019 at 18:18, Arup Rakshit <ar at zeit.io> wrote:
>> I am reading a book where the author says that:
>>
>> In principle, it would also be possible to implement any @staticmethod completely outside of the class at module scope without any loss of functionality — so you may want to consider carefully whether a particular function should be a module scope function or a static method. The @staticmethod decorator merely facilitates a particular organisation of the code allowing us to place what could otherwise be free functions within classes.
>>
>> I didn’t get quiet well this block of text. My first question is how would I make a module level function as static method of a class. Can anyone give me an example of this? What are the contexts that would let you to think if they are good fit inside the class or module level scope functions?
>
>The point the author is trying to make is that there's no practical
>difference between
>
>def say_hello(name):
> print("Hello,", name)
>
>and
>
>class Talker:
> @staticmethod
> def say_hello(name):
> print("Hello,", name)
>
>You refer to the first as "say_hello", and the second as
>"Talker.say_hello", but otherwise they are used identically. The
>static method has no access to the class or instance variables, so it
>has no special capabilities that the standalone "say_hello" function
>has. So, to rephrase the words you used, @staticmethod lets you
>organise your code in a certain way, but doesn't offer any extra
>capabilities over module-level functions.
This is true in the narrow sense that the function itself has no access
to any class of instance implicit context.
However, the method's _name_ is in the class namespace and findable from
the class or any instance.
This means that in your example above, if I have a Talker instance:
talker = Talker(....)
I can access the class _appropriate_ say_hello() function from the
instance:
talker.say_hello("Paul")
Compare this with another class:
class Writer:
@staticmethod
def say_hello(name):
global_pen.transcribe_text("Hello " + name)
If I've got a Writer instead of a talker, or better still a mix of them,
I can call their .say_hello() methods without caring what their backend
is:
leader = "Arup"
for member in social_group:
member.say_hello(leader)
So to Arup's question, a @staticmethod does not need any access to the
instance or class context. However, there are still good reasons for
putting it in the class definition:
- code organisation: you find this function in the class definition with
all the other methods
- instance access to the _appropriate_ version of the method because the
instance finds the method name in its own class.
Now, to the converse question: if you've a potential @staticmethod, why
would you bother?
Aside from accessing it via an instance (or class), marking a method as
a staticmethod has at least 2 other advantages:
It means you can call it via an instance or a class:
# use speech
Talker.say_hello("Cameron")
# use writing
Writer.say_hello("Cameron")
# use whatever "someone" prefers
someone.say_hello("Cameron")
Importantly, if it were an ordinary method:
class Talker:
def say_hello(self, name):
print("Hello", name)
then you _couldn't_ use the Talker.say_hello() or Writer.say_hello()
forms because you've got nothing for the "self" parameter.
It also makes linters happier.
What's a linter? It is a tool to inspect code and complain about all
sorts of dubious things. They're incredibly useful. Complaint vary from
cosmetic, such as poor style (which tends to correlate with hard to
ready and maintain code) to semntic, such as variables which are used
before they are initialised (which usually indicates a bug in the code,
often as simple as a misspelt variable name but also frequently geniuine
logic bugs).
In the case of a static method, if cosider these two:
class Talker:
def say_hello(self, name):
print("Hello", name)
@staticmethod
def say_hello2(name):
print("Hello", name)
You can call either from an instance:
someone.say_hello("Arup")
and get the same result. However, a linter will complain about the
former say_hello() method because the parameter "self" is unused. It
won't complain about the latter because there isn't a "self" method.
You might not care here, but if you use linters you will find complaints
about unused parameters useful. They generally indicate things like:
- you misspelt the parameter name inside the function (or conversely in
the header) so that it isn't correct. This is a bug and the linter is
helping you here.
- your function genuinely isn't using the parameter. This often
indicates that the function is not completely implemented, because
changing the value of the parameters does not affect what the function
does: that parameter is useless.
So you can see that usually these lint complaints help you find bugs in
your code before it runs; that is almost always faster and easier.
Finding bugs when the code runs requires you to actually use it in a way
that triggers the bug, which isn't always the case - it sometimes isn't
even easy if the bug is something that rarely occurs.
So, once you start using linters what is your goal? To make their
complaint output empty, because thatt makes new problems in you code
obvious.
A good linter has two basic ways to remove a complaint: change the code
to conform to the linter's rule (the usual case) _or_ mark the code in
some way to indicate that the rule should not apply here (infrequent,
but sometimes necessary - often this involves special type of comment
recognised by the linter).
However, you can consider the @staticmethod decorator to be a way to
tell linters that this method does not use "self", because (a) you're
not using "self" and (b) it lets you omit "self" from the function
header.
The third method of these two is to adjust the linter's rules,
particularly around style; I use 2 space indents in my personal code and
run my linters with a special setting for that, as 4 spaces is the
common default.
Cheers,
Cameron Simpson <cs at cskk.id.au>
More information about the Python-list
mailing list