Curious : Why staticmethod if classmethods can do everything a static method can?
This question is to further my understanding of the internals. It seems to me that a classmethod can do everything a staticmethod can, and additionally is not limited by inheritance. Why does python have them as two different constructs?
On 11Sep2020 22:58, The Nomadic Coder <atemysemicolon@gmail.com> wrote:
This question is to further my understanding of the internals. It seems to me that a classmethod can do everything a staticmethod can, and additionally is not limited by inheritance.
Why does python have them as two different constructs?
It has to do with context. What context does the method need? The default (an instance method) requires "self" to perform. A class method requires the class to perform, but not an instance. A static method requires neither. Define your method appropriately. When you start using linters to catch mechanical code issues, the "unused variable" is a standard issue: it means either that the function has been given context it doesn't need (if the variable came from a parameter), or that a function is doing a computation it doesn't need to make (or keep), or that you've got a bug because you compute something and fail to use it (often a variable name misspelling). 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. Also, what's inheritance to do with this? You can inherit static methods, I do it all the time. Maybe I don't understand what you mean by "additionally is not limited by inheritance"? Cheers, Cameron Simpson <cs@cskk.id.au>
On 2020-09-12 at 09:57:10 +1000, Cameron Simpson <cs@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? 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. 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. 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. In other languages (*cough* Java *cough*), there are no functions outside of classes, and static methods fill that role.
On Fri, Sep 11, 2020 at 7:27 PM <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
But no extraneous cognitive noise? 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. I can accomplish the same thing with clean module-level function, modulo the specific namespace in which the function is created.
Exactly. This question asked why staticmethod when we have classmethod. But the real question is why staticmethod at all? And the answer is that there is very little use for staticmethod in Python -- all it does is put a regular function in the class' namespace, and since we have modules to be nice namespaces for functions, there is little need for it. Sometimes it does make sense to keep some functionality all bundled together with a class, but I find it pretty rare. -CHB -- Christopher Barker, PhD Python Language Consulting - Teaching - Scientific Software Development - Desktop GUI and Web Development - wxPython, numpy, scipy, Cython
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 <2QdxY4RzWzUUiLuE@potatochowder.com> wrote:
On 2020-09-12 at 09:57:10 +1000, Cameron Simpson <cs@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 <cs@cskk.id.au>
On 2020-09-12 at 14:07:57 +1000, Cameron Simpson <cs@cskk.id.au> wrote:
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.
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.
Maybe what I needed early on was a good example of a large, well designed OO piece of software to use as a role model. But the large non-OO systems seemed to be much better (in any number of ways, and please don't mistake me for implying that all non-OO systems are good) than the large OO ones. My habits and opinions are certainly shaped from my experience. Namespaces are one honking great idea. I¹ have modules, you¹ have classes. We¹ build software. Vive la différence! :-)
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.
Encapsulation is a good thing, OO or otherwise. Unless, of course, the code is behind an HTTP server and I can't see it from the outside. ¹ in the generic sense of the pronoun -- “Whoever undertakes to set himself up as a judge of Truth and Knowledge is shipwrecked by the laughter of the gods.” – Albert Einstein Dan Sommers, http://www.tombstonezero.net/dan
On Fri, Sep 11, 2020, at 19:57, Cameron Simpson wrote:
The default (an instance method) requires "self" to perform.
Of course, this is only the default if the method is a function object. If it is a different callable class, the default is effectively staticmethod. Perhaps there should be an @instancemethod?
On 9/12/2020 7:13 PM, Random832 wrote:
On Fri, Sep 11, 2020, at 19:57, Cameron Simpson wrote:
The default (an instance method) requires "self" to perform. Of course, this is only the default if the method is a function object. If it is a different callable class, the default is effectively staticmethod.
Perhaps there should be an @instancemethod?
What would that let us do that we can't currently achieve? Eric
On Sat, Sep 12, 2020 at 07:25:30PM -0400, Eric V. Smith wrote:
On 9/12/2020 7:13 PM, Random832 wrote:
On Fri, Sep 11, 2020, at 19:57, Cameron Simpson wrote:
The default (an instance method) requires "self" to perform. Of course, this is only the default if the method is a function object. If it is a different callable class, the default is effectively staticmethod.
Perhaps there should be an @instancemethod?
What would that let us do that we can't currently achieve?
We already have an instancemethod, it's just spelled differently: py> from types import MethodType And while it is not useful as a decorator, it is *really* useful for adding methods to an individual instance rather than the class: py> class K: ... pass ... py> obj = K() py> obj.method = lambda self, x: (self, x) py> obj.method('arg') # Fails Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: <lambda>() missing 1 required positional argument: 'x' But this works: py> obj.method = MethodType(lambda self, x: (self, x), obj) py> obj.method('arg') (<__main__.K object at 0x7f67a4e051d0>, 'arg') So an instancemethod decorator would be a waste of time, but the instancemethod type, spelled types.MethodType, is very useful. -- Steve
On Sat, Sep 12, 2020, at 23:14, Steven D'Aprano wrote:
We already have an instancemethod, it's just spelled differently:
py> from types import MethodType
And while it is not useful as a decorator, it is *really* useful for adding methods to an individual instance rather than the class:
This isn't what I was suggesting - I meant something like this: class instancemethod: def __init__(self, wrapped): self.wrapped = wrapped def __get__(self, obj, objtype): if obj is None: return self.wrapped else: return MethodType(self.wrapped, obj) this wouldn't be useful for functions, but would give other callables the same functionality as functions, automatically creating the bound method object, e.g.: class D: def __init__(self, obj, *args): ... class C: foo = instancemethod(D) obj = C() obj.foo(...) # D(obj, ...) Same for other types of callables such as functools.partial objects etc.
So an instancemethod decorator would be a waste of time, but the instancemethod type, spelled types.MethodType, is very useful.
On Sun, Sep 13, 2020 at 12:32:54AM -0400, Random832 wrote:
This isn't what I was suggesting - I meant something like this:
class instancemethod: def __init__(self, wrapped): self.wrapped = wrapped def __get__(self, obj, objtype): if obj is None: return self.wrapped else: return MethodType(self.wrapped, obj)
this wouldn't be useful for functions, but would give other callables the same functionality as functions, automatically creating the bound method object, e.g.:
class D: def __init__(self, obj, *args): ...
class C: foo = instancemethod(D)
You want a method which does absolutely nothing at all but delegate to a class constructor or callable object (but not a function), with no docstring and no pre-processing of arguments or post-processing of the result. Seems like an awfully small niche for this to be in the stdlib. But having said that, I might have a use for that too. Except... I would need a docstring. And pre- and post-processing. Hmmm. *shrug* Seems to me that this might be useful in theory, but in practice we might never use it, preferring this instead: class C: def foo(self, *args): """Doc string.""" return D(self, *args) with appropriate pre- and post-processing as needed. Interesting suggestion though, I may have to play around with it. -- Steve
The immediate use case I can think of this for is to make it possible to just do: __len__ = instancemethod(operator.attrgetter('_length')) __hash__ = instancemethod(operator.attrgetter('_cached_hash')) and stuff like that. Mostly just a minor performance optimization to avoid the overhead of Python level function calls (and in the case of stuff like __hash__, reduce the probability of the GIL releasing during a dict/set operation, though you'd still need some way to move __eq__ to the C layer to allow stuff like setdefault to be truly atomic). This was possible in Python 2 with types.MethodType (since it could make unbound methods of a class for you; unbound methods no longer exist). Now the solution is... considerably uglier (requires ctypes): https://stackoverflow.com/q/40120596/364696 On Sun, Sep 13, 2020 at 5:24 AM Steven D'Aprano <steve@pearwood.info> wrote:
On Sun, Sep 13, 2020 at 12:32:54AM -0400, Random832 wrote:
This isn't what I was suggesting - I meant something like this:
class instancemethod: def __init__(self, wrapped): self.wrapped = wrapped def __get__(self, obj, objtype): if obj is None: return self.wrapped else: return MethodType(self.wrapped, obj)
this wouldn't be useful for functions, but would give other callables the same functionality as functions, automatically creating the bound method object, e.g.:
class D: def __init__(self, obj, *args): ...
class C: foo = instancemethod(D)
You want a method which does absolutely nothing at all but delegate to a class constructor or callable object (but not a function), with no docstring and no pre-processing of arguments or post-processing of the result.
Seems like an awfully small niche for this to be in the stdlib.
But having said that, I might have a use for that too. Except... I would need a docstring. And pre- and post-processing. Hmmm.
*shrug*
Seems to me that this might be useful in theory, but in practice we might never use it, preferring this instead:
class C: def foo(self, *args): """Doc string.""" return D(self, *args)
with appropriate pre- and post-processing as needed.
Interesting suggestion though, I may have to play around with it.
-- Steve _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@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/QKAA54... Code of Conduct: http://python.org/psf/codeofconduct/
staticmethod is generally used if you don't want an automatically bound 'self'/'cls' parameter when invoking a method on the type. classmethod just changes WHICH object is used as the "self" parameter.
On Fri, Sep 11, 2020 at 4:00 PM The Nomadic Coder <atemysemicolon@gmail.com> wrote:
This question is to further my understanding of the internals. It seems to me that a classmethod can do everything a staticmethod can, and additionally is not limited by inheritance.
Why does python have them as two different constructs?
The answer is actually somewhat embarrassing. IIRC I had heard of this concept in another language (C++? Smalltalk?) and it sounded useful, so I added staticmethod. Then after the release and some actual use I realized that it was actually useful to have access to the class from inside the method (in case it's called for a subclass). So I also added classmethod. But since staticmethod was already out of the bag, I kept it around, and it's found its uses (as you can see from other replies). -- --Guido van Rossum (python.org/~guido) *Pronouns: he/him **(why is my pronoun here?)* <http://feministing.com/2015/02/03/how-using-they-as-a-singular-pronoun-can-c...>
On 12/09/20 12:21 pm, Guido van Rossum wrote:
I had heard of this concept in another language (C++? Smalltalk?)
Probably C++ or Java. Smalltalk doesn't have static methods, only a form of class method. IMO, static methods are a kludge only needed in languages that make classes do double duty as modules, and also can't have class methods because classes aren't first-class objects. I've never felt the need for one in Python. -- Greg
On 9/11/20 7:35 PM, Christopher Barker wrote: ---------------------------------------------
But the real question is why staticmethod at all?
And the answer is that there is very little use for staticmethod in Python [...]
On 9/11/20 7:28 PM, Greg Ewing wrote: -------------------------------------
IMO, static methods are a kludge only needed in languages that make classes do double duty as modules, and also can't have class methods because classes aren't first-class objects.
--------------------------------------------- I have a base class that holds the logic for converting from one data system to another. The module that holds this base class also imports several utility functions from other places. The subclasses that hold the unique code occasionally have to overwrite/extend some of the base class methods, which means also needing to import the utility functions (the subclasses live in different files). Rather than having to keep track of the various utility methods, including adding more imports if the base class uses more with new functionality, I just get_fis_table = staticmethod(fisData) get_xid_records = staticmethod(get_xid_records) and then the subclasses can do blah = self.get_xid_records(...) without worrying where `get_xid_records` came from. Without `staticmethod` I would have to have a thin wrapper around those utility methods -- obviously not insurmountable, but annoying; and I really like that Python is not usually annoying. -- ~Ethan~
participants (12)
-
2QdxY4RzWzUUiLuE@potatochowder.com
-
Cameron Simpson
-
Christopher Barker
-
Eric V. Smith
-
Ethan Furman
-
Greg Ewing
-
Guido van Rossum
-
Josh Rosenberg
-
Random832
-
Steven D'Aprano
-
The Nomadic Coder
-
William Pickard