[Tutor] Difference(s) betweenPython 3 static methods with and without @staticmethod?

boB Stepp robertvstepp at gmail.com
Mon Aug 7 21:22:40 EDT 2017


I feel like I have gazed into a crystal clear pool, apparently
shallow, with many interesting objects (Pun intended!) on the pool
floor.  Interested in these beautiful things, I jump in to grab one
for close-up study only to find that the pool is much, ... , much
deeper (> 1 boB-height) than it appeared.  And alas!  I don't know how
to swim!

On Mon, Aug 7, 2017 at 8:36 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Sun, Aug 06, 2017 at 06:35:10PM -0500, boB Stepp wrote:
>
>> py3: class MyClass:
>> ...     def my_method():
>> ...             print('This is my_method in MyClass!')
>> ...
>> py3: class MyOtherClass:
>> ...     @staticmethod
>> ...     def my_other_method():
>> ...             print('This is my_other_method in MyOtherClass!')
>> ...
>> py3: MyClass.my_method()
>> This is my_method in MyClass!
>> py3: MyOtherClass.my_other_method()
>> This is my_other_method in MyOtherClass!
>
> A small comment: since the methods have different names, there's no
> reason why they couldn't both be in the same class.

As I did not know exactly where I was going with these exploratory
examples, I thought I would keep things separated in case something
surprising developed.  Then I could easily compare the two.

> It is hard to see the distinction between an ordinary method and a
> static method from this example. What you are seeing is, by accident,
> the equivalent of just writing:
>
> def my_method():
>     print('This is my_method in MyClass!')
>
> outside of the class and calling the bare function. Notice that despite
> the name, my_method is not *actually* usable as a method. It has no
> "self" parameter.

I thought that was the whole essence of static methods -- no self parameter.

>
>> I see no difference in result, whether I use the @staticmethod decorator or not.
>
> Indeed you would not, because you are calling MyClass.my_method,
> directly from the class object. If you created an instance first, and
> then called the methods, you would see a big difference!

Alan pointed out I might want to investigate this.  I did and got what
you show below.

> obj = MyClass()
> obj.my_method()  # fails with TypeError, as there is no "self" parameter
>
> while the staticmethod would succeed.
>
> So what's going on here?
>
> When you call MyClass.my_method, Python looks up my_method on the class
> object, equivalent to:
>
>     thing = getattr(MyClass, 'my_method')
>
> before calling thing(), which then prints the message.
>
> This is where is gets complex... Python performs some special magic when
> you look up attributes, according to "the descriptor protocol". Don't
> worry about how it works -- think of it as black magic for now. But the
> bottom line is, if the look up returns something like a string or an int
> or a list, no descriptor magic happens, but if it returns a function
> (something you created with "def"), something not far removed from this
> happens:
>
>     # (instance) method descriptor
>     if thing is a function:
>         if the source was a class # like "MyClass.method":
>             # This was different in Python 2.
>             return thing, unchanged
>         else:  # source was an instance, "MyClass().method"
>             wrap thing in a method object that receives "self"
>             return the method object
>
> Why does it bother? Because that way Python can automatically fill in
> the instance as "self".

[snip]

>
> Despite the name, your example "my_method" isn't a method and doesn't
> require "self". If you try to pass one in, you get a TypeError because
> the function doesn't take any arguments at all. But normally methods
> need to know what instance they are working on, in other words, they
> need to know "self".

Question(s):  While something like "my_method" can't be called from an
object instance, it can be called on the class.  Are there use cases
where this behavior is actually desired?  If wrapped with
"@staticmethod" then there are two ways of calling the method, using
objects or using the class.  Is there some reason not to use the
"ClassName.a_static_method()" syntax?  Are there intended uses for
doing this?

> So when you call MyClass.my_method, calling from the class, Python just
> gives you the raw function object, unchanged. Normally that means you
> would need to provide "self" yourself. This is called an "unbound
> method", it's unbound because it doesn't know what instance to work on.

[snip]

> Unbound methods have their uses. In Python 2, they were actual custom
> method objects, but in Python 3 it was decided that this was pointless
> and instead the bare function is returned instead. But conceptually, the
> base function is an unbound method.
>
> So normal methods convert a function to a method which automatically
> provides "self", but only if called from the instance (self). Otherwise
> they return the function, which needs you to manually provide self.
>
> In your example, by accident (I assume?) ...

No, on purpose.  I was just playing around to see what would or would
not happen.

> ... you forgot to include "self",
> so the fact that no self was provided didn't stop the function from
> working.
>
> Now, how about class methods? Class methods are just like ordinary
> (instance) methods, except instead of "self", they receive the class
> object as first parameter. There aren't many good examples of class
> methods, and so they aren't common. But here's one:
>
>     dict.fromkeys
>
> Most dict methods (update, pop, popitem, clear, etc.) need to know which
> specific dict to work on. But fromkeys is different: it does not care
> what instance you use. Both of these will work exactly the same:
>
>     {}.fromkeys(['one', 'two', 'three'])
>     {100: 'A', 200: 'B', 300: 'C'}.fromkeys(['one', 'two', 'three'])
>
> The instance is so unimportant that fromkeys can be equally called from
> the dict class itself:
>
>     dict.fromkeys(['one', 'two', 'three'])
>
> Instead of the instance, "self", fromkeys receives the class, "cls". In
> this case, it will be dict, but if you subclass:
>
> class MyDict(dict):
>     pass
>
> MyDict.fromkeys(['one', 'two', 'three'])
>
> then the fromkeys method sees *your* class, MyDict, instead of dict. To
> make your own functions that behave like fromkeys, you decorate them
> with the `classmethod` built-in:
>
> class MyClass(object):
>     @classmethod
>     def method(cls, arg):
>         ...
>
> When you use classmethod, the behaviour you see is something like:
>
>     # classmethod descriptor
>     if thing is a function:
>         wrap thing in a classmethod object that receives "cls"
>         return the classmethod object

Class methods look to be something I will find difficult to find a use for.

>
> Finally we come to static methods, which use the `staticmethod`
> decorator. Static methods sometimes are confusing, because:
>
> (1) There are very, very few good uses for static methods in Python. If
> you think you need a static method, you probably could just use a
> regular module-level function.

This is how I am currently seeing things.


>> In "Python Pocket Reference, 5th ed." by Mark Lutz, on page 151 under
>> the entry "staticmethod(function)" the author states, "... Use the
>> @staticmethod functiion decorator in version 2.4 and later ... In
>> Python 3.X only, this built-in is not required for simple functions in
>> classes called only through class objects (and never through instance
>> objects)."  So what I am understanding is that I only need use the
>> @staticmethod decorator if I am using Python versions 2.4 through 2.7
>> (new-style classes).  Is my understanding correct?
>
> What Mark is saying here is that in Python 2, if you want the static
> method functionality, you have to use the staticmethod decorator:
>
> class MyClass(object):
>     @staticmethod
>     def func(arg):  # no self, no cls
>         ...
>
>
> But in Python 3, you can leave it out, *provided* you never call
>
> instance = MyClass()
> instance.func(x)  # TypeError
>
> but only
>
> MyClass.func(x)

Between you and Alan, I understand his comments now.

>
> I don't know why he bothered to mention that. The fact that this works
> at all is an accident, not something to rely on.

Not knowing Mr. Lutz, except through his books, he strikes me as a
very thorough person.

Thanks, Steve!  Your examples helped a lot.  Between this post of
yours and the other one in the thread "basic decorator question", I
think I have a decent handle on the ideas, if not all the details.
And Eryk's answer did push me to dip my toe into the descriptor pool a
little bit.  I think a lot of dedicated study on my part will be
needed on these topics.



-- 
boB


More information about the Tutor mailing list