[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