On 12/14/2011 3:01 AM, Steven D'Aprano wrote:
On Wed, 14 Dec 2011 01:29:13 -0500, Terry Reedy wrote:
To complement what Eric says below: The with statement is looking for an
instance *method*, which by definition, is a function attribute of a
*class* (the class of the context manager) that takes an instance of the
class as its first parameter.
I'm not sure that is correct... I don't think that there is anything "by
definition" about where methods live.
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.
Particularly not in Python where
instance methods can be attributes of the instance itself.
This is access, not definition or actual location. 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. 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).
class Test(object):
... def method(self):
... print("This method is an attribute of the class.")
...
t = Test()
t.method()
This method is an attribute of the class.
The bound method t.method is an instance the class exposed as
types.MethodType. In other words, isinstance(t.method, types.MethodType)
== True
import types
t.method = types.MethodType(
... lambda self: print(
... "This method is an attribute of the instance."), t)
Calling any old fruit an apple does not make it one.
Calling any old function a method does not make it one.
'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. So the printed statement is not true.
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.
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.
t.method()
This method is an attribute of the instance.
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. 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()
f = t.method
f()
t.method = lambda: print("This method is an attribute of the instance.")
t.method()
So the normal lookup rules that apply to data attributes, namely
instance, then class, then superclasses, also applies to methods in
Python.
When you ask the interpreter to resolve a.x, x is just a supposed
attribute, and the interpreter has no idea what class the result should be.
But this doesn't apply for special dunder attributes like __exit__, for
speed reasons.
It does not apply to dunder *methods* because they are reserved names
defined to be (bound to) methods. So the interpreter knowing that it is
looking for a method and that methods have to be attributes of classes,
goes directly to the class.
--
Terry Jan Reedy
--
http://mail.python.org/mailman/listinfo/python-list