AttributeError in "with" statement (3.2.2)

Terry Reedy tjreedy at udel.edu
Wed Dec 14 18:13:36 EST 2011


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




More information about the Python-list mailing list