Dan, I should preface this by saying I don't substantially disagree with 
you, I just work differently and want to show how and why.

On 11Sep2020 21:24, Dan Sommers <2qdxy4rzwzuui...@potatochowder.com> wrote:
>On 2020-09-12 at 09:57:10 +1000,
>Cameron Simpson <c...@cskk.id.au> wrote:
>> So, consider:
>>
>>     @classmethod
>>     def func(cls, foo):
>>         print(foo)
>>
>> A linter will warn you that "cls" is unused. With a static method:
>>
>>     @staticmethod
>>     def func(foo):
>>         print(foo)
>>
>> a linter will be happy.
>>
>> Think of @classmethod and @staticmethod as ways to write "clean"
>> functions with no extraneous cognitive noise.
>
>I concur with all of Cameron's technical details and explanations.
>
>But no extraneous cognitive noise?

Specificly in the sense that the @classmethod example has an unused 
"cls" parameter. For a three line function this is trivial to ignore, 
but for a substantial function I'd be devoting annoying brainpower to 
understanding where it was used, and if it didn't appear, _should_ it be 
used? Thus noise. With the static method it is instantly clear that no 
instance or class context is used.

>By definition, methods appear inside
>a class definition, and then I have to process the @staticmethod
>decorator.  Effectively, the decorator "cancels" the class method status
>of the function.

For me, I have to process either the @staticmethod or @classmethod 
decorators equally - they just have different meanings. But at least 
they're up the front - I see them and their meaning for the function 
context is instantly available to me instead of needing to be deduced.

>I can accomplish the same thing with clean
>module-level function, modulo the specific namespace in which the
>function is created.
>
>So, in a module m:
>
>    class X:
>        @staticmethod
>        def f(x):
>            print(x)
>
>and
>
>    def f(x):
>        print(x)
>
>m.X.f and m.f are interchangeable.

Yes, technically. Let me say up front that I write plenty of module 
elvel plain functions.

However I do have plenty of use cases for @staticmethod in class 
definitions. They tend to be class utility functions, either for some 
common function used in the class methods, _or_ a common method meant to 
be available for outside use - particularly for a common pattern for 
outside use.

Here's an example of the former:

      @staticmethod
      def pick_value(float_value, string_value, structured_value):
        ''' Chose amongst the values available.
        '''
        if float_value is None:
          if string_value is None:
            return structured_value
          return string_value
        return float_value

      @property
      def value(self):
        ''' Return the value for this `Tag`.
        '''
        return self.pick_value(
            self.float_value, self.string_value, self.structured_value
        )

This is from a tag library of mine, where the tags live in a database 
and there are three fields: a float, a string and a JSON blob. Only one 
will be nonNULL. This supports a remarkable flexibility in tagging 
things. It is common in the class to pick amongst them, thus the static 
method. It is also not unreasonable to work with such a choice outside 
the class (so it might not be a private method/function).

On review, I've actually got relatively few examples of the latter 
category - methods for use outside the class - they are almost entirely 
@classmethods. The few which are static methods live in the class for 
purposes of conceptual hierarchy.

My commonest example is the transcription method from a binary structure 
module I have. Data structures each have a class which implements them, 
and all subclass a base class. This gets me a common API for parsing the 
structure and also for writing it back out.

When implementing a particular structure one always has a choice between 
implementing one of two "transcribe" methods. For a simple thing with a 
single "value" (eg an int) you'd implement "transcribe_value(value)", a 
static method. For something more complex you implement "transcribe()", 
a class or instance method depending. In the abstract base class each 
method calls the other - the subclass must provide a concrete 
implementation of one method.

Anyway, back to the static method: if is pretty common to want to write 
out a value using the transcription from a class, example:

    value = 9
    bs = BSUInt.transcribe_value(value)

without going to any hassle of instantiating an instance of BSUInt - we 
just want to convert from the Python type to bytes. There's a similar 
example for parsing.

The beauty here is that you have the same pattern of
classname.transcribe_value(value) to use whatever binary format you 
want.  And that's a static method because it doesn't need a class or 
instance for context - it just transcribes the data.

>IMO, m.f has less cognitive load
>than m.X.f, at their definitions and at call sites.  Full disclosure:  I
>consider most of Object Oriented Programming to be extraneous cognitive
>noise.

Whereas I use OOP a lot. Hugely.

>In other languages (*cough* Java *cough*), there are no functions
>outside of classes, and static methods fill that role.

Aye. I agree that is an example where static methods exist for language 
definition reasons instead of functionality. OTOH, the language design 
is deliberately like that to force encapsulation as a universal 
approach, which has its own benefits in terms of side effect limitation 
and code grouping, so there's an ulterior purpose there.

Cheers,
Cameron Simpson <c...@cskk.id.au>
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/BRQWECARBKVJWCLKYRXIDHWCDBVI65EY/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to