AttributeError in "with" statement (3.2.2)

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Dec 16 04:22:35 EST 2011


On Thu, 15 Dec 2011 19:39:17 -0500, Terry Reedy wrote:
[...]

After reading your post, I think I have worked out where our disagreement 
lines: you think that bound methods and instance methods are not the same 
thing, and that a function defined inside a class is different from a 
function outside of a class.

For example, you say:

> If so, the output is a *bound method*. In your example
> above, func is not an instance method and obj is not a bound method. It
> is simply an partially evaluated curried function or if you prefer, a
> bound function. Take you pick, or make up your own term, but it is NOT
> an instance method, 

and later on:

>> So what are methods? In Python, methods are wrappers around functions
>> which automatically pass the instance to the inner function object.
> 
> These are bound methods. The instance methods are the functions wrapped.

I am afraid you are mistaken. What you say may very well apply to other 
languages, but in Python, def creates functions no matter where you 
execute it. Always and without exception.

I admit to an earlier mistake: I thought that conversion from function to 
method occurred once, when the class statement was executed, but I was 
mistaken. Re-reading Raymond Hettinger's excellent article on the 
descriptor protocol reminded me that methods are created on an as-needed 
basis, at runtime.

Back to methods and def. So let's see what happens in Python 3.1:

>>> def spam(self):  # Clearly a function.
...    pass
... 
>>> class K(object):
...     def ham(self):  # Allegedly an instance method
...             pass
... 

According to your various statements, spam is a function and ham is an 
instance method. Merely storing a outside function inside a class doesn't 
create an instance method, it creates what you call a bound method 
(although I'm unclear what you think the difference is). So let's see 
what happens when we compare an alleged "bound method that is not an 
instance method" with an actual instance method:

>>> K.spam = spam
>>> type(K.spam) is type(K.ham)
True

Their types are identical. We get the same thing when we compare an 
actual function with a function object created inside a class, which you 
claim is actually an instance method:

>>> type(spam) is type(K.__dict__['ham'])  # Bypass descriptor protocol
True

Nor is there any difference in type between bound and unbound methods: 
they are both instance methods differing only in whether or not they have 
the first argument "self" available. Bound is an adjective, not part of 
the type: small method, obfuscated method, buggy method, or bound method.

Python 3 no longer uses unbound methods, since they are functionally 
identical to the unwrapped function, so this snippet is from Python 2.6:

py> k = K()
py> type(k.ham) is type(K().ham)  # bound vs bound
True
py> type(k.ham) is type(K.ham)  # bound vs unbound
True


In Python, instance methods are wrappers around function objects; they 
are created on call, and generally do not exist *anywhere* until needed, 
or if you store a reference to them.

>>> k = K()
>>> a = k.ham
>>> b = k.ham
>>> a is b
False

Under normal circumstances, the only persistent object is the function, 
which you can extract from the (class or instance) __dict__ or the method 
wrapper:

>>> a.__func__ is K.__dict__['ham']
True

Methods are created by the descriptor protocol: when you use the normal 
a.b syntax to access K.__dict__['ham'], the metaclass calls the __get__ 
method of function ham. If you call it from an instance, you get a method 
object bound to that instance. If you call it from the class, in Python 2 
you get a method object not bound to an instance, in Python 3 you get the 
function without a wrapper.

[Aside: class methods and static methods also work the same way, via 
__get__ and the descriptor protocol, but behave differently. Class 
methods reuse the instance method type, which is somewhat confusing, and 
static methods just return the function.]

So, what the hell does all this mean in practice?

Most of the time, absolutely nothing. That's why I started this 
discussion with the disclaimer that I didn't think it was worth changing 
the (not quite right) definition of "method" you originally quoted. Most 
of the time, we only access methods via the instance.method syntax, which 
gives us an instance method. By the principle that if it quacks like a 
duck and swims like a duck, it is good enough to call it a duck, I'm 
happy to agree that ham here is a method:

class K:
    def ham(self): pass 

That covers 99% of all use-cases and is good enough for most situations.

But the OP ran into one of those edge cases in the 1%, where things are 
not so simple. He was attempting to create an instance method called 
"__exit__" and store it in the instance __dict__ instead of the class 
__dict__. This is a perfectly reasonable thing to do, but there are two 
gotchas to it:

  * The descriptor protocol doesn't get used for instance 
    lookups. That's why you can't stick properties in an 
    instance, only in the class.

  * CPython, and I believe Jython, don't look up special 
    dunder methods like __exit__ on the instance, only on
    the class.

So even if the OP manually created an instance method rather than relying 
on the descriptor protocol, his per-instance __exit__ would not be called 
without extra work.



-- 
Steven



More information about the Python-list mailing list