From the Python glossary:
"method: A function which is defined inside a class body."
That is actually a bit too narrow, as a function can be added to the
class after it is defined. But the point then is that it is treated as
if defined inside the class body.
First off, let me preface this by saying that I'm not necessarily saying
that the above glossary definition needs to be changed. For most
purposes, it is fine, since *nearly always* methods are created as
functions defined inside the class body. But it needs to be understood in
context as a simplified, hand-wavy definition which covers 99% of the
common cases, and not a precise, definitive technical definition.
To give an analogy, it is like defining mammals as "hairy animals which
give birth to live young", which is correct for all mammals except for
monotremes, which are mammals which lay eggs.
So I'm happy for the glossary entry to stay as is, because complicating
it would confuse the average coder more than it would educate them.
But the definition as given is strictly wrong, because it fails to
capture the essence of what distinguishes a method from other objects. It
mistakes *how* you normally create a method for *what* a method is, a
little like defining "a hamburger is a foodstuff you get from McDonalds
by giving them money".
Here are three ways that the definition fails:
(1) You can create a function inside a class, and it remains a function,
so long as the class constructor (metaclass) never gets to build a method
object from the function. It is easy to do: just hide it inside a wrapper
object.
.... def func(x, y): # define a function inside a class
.... return x + 2*y
.... print(type(func)) # confirm is actually is a function
.... attr = (func,) # hide it from the metaclass
.... del func
....
print(type(FunctionInsideClass.attr[0]))
<class 'function'>
(2) Instead of hiding the function from the metaclass, you can change the
metaclass to something which doesn't make methods out of functions. I
won't show an example, because it's tricky to get right (or at least *I*
find metaclasses tricky).
(3) So the definition is too broad: you can have functions defined inside
classes that are not methods. But it is also too narrow: you can have
methods outside of classes. I'm not talking about bound and unbound
methods, but about *creating* the method from scratch outside of a class.
When you call the method constructor directly, you can create a method
from a function defined outside of a class.
.... return self + a
....<class 'method'>
So there's a method which has never been inside a class, and couldn't
even if you tried: int is a built-in immutable type.
So what are methods? In Python, methods are wrappers around functions
which automatically pass the instance to the inner function object. Under
normal circumstances, you create methods by declaring functions inside a
class, but that's not the only way to create methods, and it is not the
only place they can be found.
Note that this definition can easily be adapted to describe class methods
and static methods too: class methods are wrappers around functions that
automatically pass the class to the inner function, and static methods
are wrappers which don't pass any automatic arguments.
This is access, not definition or actual location.
Not so. In the example I gave, the method *really is* inside the
instance, stored in the instance __dict__ and not the class __dict__.
The glossary entry go
on to say: "If called as an attribute of an instance of that class, the
method will get the instance object as its first argument (which is
usually called self)." This does *not* happen if a callable is found in
the instance-specific dictionary.
That's right. Methods are special not because of where they are, but
because of what they are.
An instance method is a function
(callable) attribute of a class that gets special treatment when
accessed (indirectly) through an instance of that class (or subclass
thereof).
Methods aren't functions at all, not in the isinstance sense. They are
functions in the sense that any callable object is a function, i.e. they
are callable sub-routines. But they're not functions in the sense of
being instances of type "function". They are wrappers around functions.
Other languages may do things differently, but that's what Python does.
You can even retrieve the function object from the wrapper:
<function func at 0xb70fd4ec>
The bound method t.method is an instance the class exposed as
types.MethodType. In other words, isinstance(t.method, types.MethodType)
== True
Calling any old fruit an apple does not make it one. Calling any old
function a method does not make it one.
I'm not *calling* a function a method, I'm *creating* a method from a
function object. There is no difference between a method created with
types.MethodType and a method automagically created inside a class except
for the location where the object is stored. The location is irrelevant.
'types.MethodType' is the exposed name of the class the interpreter uses
to create bound methods from a method and an instance of the class
containing the method. I believe the interpreter does an isinstance
check, but it must do that before calling the class, and not in the
bound method constructor itself. In any case, a bound method is not a
method.
That's an astonishing statement. What is your evidence for this amazing
claim that bound methods are not actually methods? What are they then, if
not methods?
In this case, the result is not really even a bound method, as the
function argument is not a method, so we cannot even ask if the second
arg is an instance of the function class container. MethodType is a
special case of functools.partial, which was added later. You could have
used the latter to the same effect. Or you could have used any old
function that printed the same thing.
Good grief. Is it really your argument that the types.MethodType isn't
actually the type of methods, but a fake that lies about returning
methods? Well, that's easy enough to test:
.... def f(self):
.... pass
....True
Unless you are going to accuse me of faking the interpreter output
(perhaps I monkey-patched the type built-in?) that is definitive proof
that types.MethodType is not fake, it actually is the method type used in
classes.
There is no relation between the object passed as the second arg of
MethodType and what you do with the resulting callable. Either 't' could
be something else. See below.
Yes, the callable (which is not a method) is (currently) an attribute of
the instance. But that is irrelevant to its operation. t.method is just
a callable, in particular, a pseudo bound method, not a method. It is
*not* supplying the instance it is called on as the first parameter of
the callable.
Of course it is. It is because I made it to be that way.
I encourage you to experiment with Python's introspection tools, perhaps
put a few calls to print inside a "pseudo bound method" (your words,
there's nothing pseudo about it) and satisfy yourself that the instance
passed is the same instance as it is called from *under the circumstances
shown*.
The arguemnt (which is not used) has already been
supplied. These produce the same output:
class B: pass
b = B()
b.method = t.method
b.method()
Yes. So what? You can take a bound object and attach it to any other
object and see the same results -- this doesn't mean it isn't a bound
object. That's how bound objects work! Your objection fails because any
method will work the same way:
.... def foo(self):
.... print(self)
....<__main__.Example object at 0xb70fcc0c>
No trickery with types.MethodType, no metaclass magic, no sleight of
hand, just stock standard Python behaviour.