[Tutor] method, type?

Steven D'Aprano steve at pearwood.info
Thu Jan 7 18:19:57 EST 2016


On Thu, Jan 07, 2016 at 11:42:18AM -0800, Alex Kleider wrote:
> Thank you to all who contributed to this thread.
> It has helped me immensely and I enjoyed some of the spirited 
> discussion.
> 
> Some of my notes follow (in case corrections are in order:-)
> 
>     my_notes = """
> 
>     @staticmethod
>     def s_method(param_but_no_self_or_cls):
>         # An ordinary function that resides in the class to associate
>         # its functionality with the class.
>         pass

s_method is not a function, but a static method, since you have used the 
staticmethod decorator.

(And good news: the term "static method" in Python has *nothing* to do 
with the same term as used in Java and C++. Yay.)

If you are confused, it's not surprising, as you are playing on the 
edges of Python's internals where most programmers fear to tread :-)

Ultimately, the final arbiter of "what is this thing?" is to ask type. 
So let's have a look at what happens when we create a method inside a 
class, using Python 3:

py> class Test:
...     def foo():  # intentionally given no parameters
...             pass
...     print("during class definition time", type(foo))
...
during class definition time <class 'function'>
py> print("access from the class", type(Test.foo))
access from the class <class 'function'>
py> print("access from the instance", type(Test().foo))
access from the instance <class 'method'>


So while the class is still being defined, "foo" is an ordinary 
function. def ALWAYS creates a function, no exceptions.

When you access it via the class, Test.foo, you also get a function. 
(This bit is new to Python 3 -- previously, you got an unbound method 
object, which is essentially like a function but with restrictions on 
the first argument.)

When you access it via the instance, Test().foo, you get a method. 
What's happening here? This is the magic of the descriptor protocol, 
which you don't have to understand (consider it a bit more advanced than 
classes, but less advanced than metaclasses). For now, just think if it 
like this:

When you access instance.foo, Python creates a method object that 
automatically knows the instance it was called from, and the function 
it was built from, so that it can automatically provide the "self" 
parameter when you call the method.


What happens when you use the staticmethod (or classmethod) decorator?

py> class Test:
...     @staticmethod
...     def foo():
...             pass
...     print(type(foo))
...
<class 'staticmethod'>

So right from the word go, the function foo gets converted to a 
staticmethod object. Now for one of Python's worst-kept secrets:

staticmethod only exists to prevent Python from automatically converting 
functions into (regular) methods when you access them from the instance.

Remember how I showed that instance.foo takes the underlying foo 
function, converts it into a method, and binds the instance as the first 
parameter "self"? To prevent that behaviour, you have to fool Python 
into thinking that foo is not a function. The way to do that is to turn 
it into a staticmethod, which is (almost) exactly like a function but 
doesn't have the magic turn-into-a-method behaviour of functions.

Although there are slight differences, you should consider that static 
methods in Python are semantically equivalent to top-level functions 
except that they live inside a class.

Since staticmethods don't know what class they come from, there are very 
few reasons to justify using a staticmethod. At one point, Guido wrote 
that there were no known uses for staticmethod outside of Python's test 
suite. Most times you think you want to use staticmethod, chances are 
high that you either want a class method or a global function.

 
> Instance methods expect 'self'.    | arranged implicitly when called
> Class methods expect 'cls'.        | via instance or class.

Correct.


> "factory" methods (typically called '.from_*') can be:
>     1. a normal function outside the class, or
>     2. a class method (would allow subclassing.)

"Factory methods" just means a method which you, the creator or author, 
thinks of as a factory. What's a factory? A function or method which 
takes a bunch of arguments and creates something useful.

It's a pretty vague definition, because it's a pretty vague term. The 
idea is, you have things that you use directly (ints, strings, lists, 
widgets...) and you have things which you use to create those things. 
That's a factory.

In my opinion, the *only* use of the term "factory" which adds more than 
it takes away in confusion is "factory function", meaning a function 
which creates and returns a new function. But that's not what we're 
talking about here. In this case, I don't think the term "factory" is 
useful. It just makes things seem more "Enterprisey" and Java-esque than 
it need be.

A little bit less vague is the idea of a method inside a class which 
creates an instance of that same class. That's a constructor. The 
default constructor in Python is the combination of special methods 
__new__ and __init__, so any other such method (typically called 
"from_something") is an "alternate constructor".

As for our discussions of "named constructors", I don't think they're 
relevant because they're all named constructors in Python. (With the 
possible exception of the default, depending on what you mean by a named 
constructor.)


>     "alternative constructor" (what Petter Otten and Steven DAprano
>     call it,) 

Alternative in the sense of "not the default", that is all.


>     would be best placed immediately after __init__.

*shrug* 

It doesn't matter where you put it inside the class. That is entirely a 
matter of personal taste.


>     Alan Gauld indicates that as initially written (without
>     '@staticmethod') "it is not" ?a method/function?

With respect to Alan, I think he is factually wrong. As I said, the 
final arbiter of what a thing is is the type() function, and that tells 
us that if you don't use a decorator, def always creates a function.

(Technically even if you use a decorator that is still true, but it's 
just harder to spot.)


>     but a 'named constructor' which is not supported by Python, 

But they are :-)


>     so it could be a 'class method.'

But it isn't :-)


>     He recommends making it a factory
>     function (defined at the module level, outside the class.)

That's a matter of personal taste, and one which I happen to disagree 
with. Look at the design of the built-in classes like dict. We have 
dict.fromkeys(), not a global fromkeys() function.


>     Steven DAprano calls it a Python3 regular function/ a Python2
>     broken method and mentions the Descriptor protocol and how
>     'methods' are initially simply functions that are then converted
>     to methods (bound) as required. In my case it would be an
>     'unbound' method which works in 3 but not in Python2.
> 
> Cameron Simpson indicated that putting @staticmethod above my 'method'
> would be OK (although not preferred.)  Present or absent, my method
> still functions the same way.

Only because you're just calling it from the class. As soon as you 
create an instance and call the method from that, you'll see why it 
is broken :-)



> The table provided by Peter Otten (very helpful:)
> -----------------------------------------------------------------
> invoked with | @staticmethod  | @classmethod    | no decorator
> ------------------------------------------------------------------
> class        | args unchanged | class as 1st arg | args unchanged
> instance     | args unchanged | class as 1st arg | inst as 1st arg
> -------------------------------------------------------------------
> It suggests that use of the @staticmethod serves to protect one should
> the 'method' be called via an instance rather than the class.  Has it
> any other effect?

Yes, to confuse people into thinking they should be using staticmethod 
when what they really should use is classmethod :-)



-- 
Steve


More information about the Tutor mailing list