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/