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 <st...@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 _______________________________________________ Tutor maillist - Tutor@python.org To unsubscribe or change subscription options: https://mail.python.org/mailman/listinfo/tutor