[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