S
Steven D'Aprano
]
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:
and later on:
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:
.... pass
........ 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:
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:
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.
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:
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.
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:
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:
.... pass
........ 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:
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:
Truetype(spam) is type(K.__dict__['ham']) # Bypass descriptor protocol
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.
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:
Truea.__func__ is K.__dict__['ham']
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.