osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Question about the @staticmethod decorator


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>