Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Another of those "is" issues.

2 views
Skip to first unread message

Emanuele D'Arrigo

unread,
Mar 20, 2009, 2:20:27 PM3/20/09
to
Hi everybody,

I was unit testing some code today and I eventually stumbled on one of
those "is" issues quickly solved replacing the "is" with "==". Still,
I don't quite see the sense of why these two cases are different:

>>> def aFunction():
... pass
...
>>> f = aFunction
>>> f is aFunction
True <--- Ok, this seems reasonable. Nevertheless, I suspect I
shouldn't quite rely on it.

>>> class MyClass(object):
... def myMethod(self):
... pass
...
>>> c = MyClass()
>>> m = c.myMethod
>>> m is c.myMethod
False <--- What? Why is that?

In my mind I was expecting that when the method is assigned to "m" all
that it happens is that its address is assigned to the name "m" so
that effectively the same address is now pointed to by two names, like
in the function case. I googled around for some hint but I wouldn't
exactly say I'm clear on the issue just yet...

Can anybody shed some light? Or point to a resource to look at? Or
what's the bit of python's source code that is responsible for dealing
with those assignments?

Manu


Benjamin Peterson

unread,
Mar 20, 2009, 2:34:39 PM3/20/09
to pytho...@python.org
Emanuele D'Arrigo <manu3d <at> gmail.com> writes:
>
> >>> class MyClass(object):
> ... def myMethod(self):
> ... pass
> ...
> >>> c = MyClass()
> >>> m = c.myMethod
> >>> m is c.myMethod
> False <--- What? Why is that?
>
> Can anybody shed some light? Or point to a resource to look at? Or
> what's the bit of python's source code that is responsible for dealing
> with those assignments?

Functions are descriptors (they have a __get__ method). Thus, every time a
method is resolved, a new bound method object is created with the object passed
to it.


Tim Wintle

unread,
Mar 20, 2009, 2:37:11 PM3/20/09
to pytho...@python.org
On Fri, 2009-03-20 at 11:20 -0700, Emanuele D'Arrigo wrote:
> >>> def aFunction():
> ... pass
> ...
> >>> f = aFunction
> >>> f is aFunction
> True <--- Ok, this seems reasonable. Nevertheless, I suspect I
> shouldn't quite rely on it.

You can rely on this in the above - you've just assigned the name "f" to
the same object as "aFunction"

>
> >>> class MyClass(object):
> ... def myMethod(self):
> ... pass
> ...
> >>> c = MyClass()
> >>> m = c.myMethod
> >>> m is c.myMethod
> False <--- What? Why is that?

I believe that c.myMethod is actually a new object that's similar to
lambda self,*args,**kwargs: MyClass.myMethod(self,*args,**kwargs).

ie:

The MyClass *instance* has checked if there is an object
self.__dict__["myMethod"], and when it hasn't found it it creates a new
function which wraps the call to the base class up correctly. It's more
complicated than this though because you see the same behaviour when
checking the method on the class definition.

Seem to remember Raymond Hettinger pointing to the code that does this
as part of his "descriptor tutorial" talk at europython - but I can't
find the slides to reference.

hope that helps,

Tim Wintle

Scott David Daniels

unread,
Mar 20, 2009, 2:47:53 PM3/20/09
to
Emanuele D'Arrigo wrote:
> Hi everybody, ...

>>>> f = aFunction
>>>> f is aFunction
> True <--- Ok, this seems reasonable. Nevertheless, I suspect I
> shouldn't quite rely on it.
Actually, that's fine, you are simply comparing a pair of references

>>>> class MyClass(object):
> ... def myMethod(self):
> ... pass

>>>> c = MyClass()
>>>> m = c.myMethod
>>>> m is c.myMethod
> False <--- What? Why is that?

Method access binds the instance in dynamically.

Comparing m is c.myMethod
is like comparing m is partial(MyClass.method, c)

--Scott David Daniels
Scott....@Acm.Org

Steve Holden

unread,
Mar 20, 2009, 2:48:41 PM3/20/09
to Emanuele D'Arrigo, pytho...@python.org
Emanuele D'Arrigo wrote:
> Hi everybody,
>
> I was unit testing some code today and I eventually stumbled on one of
> those "is" issues quickly solved replacing the "is" with "==". Still,
> I don't quite see the sense of why these two cases are different:
>
>>>> def aFunction():
> ... pass
> ...
>>>> f = aFunction
>>>> f is aFunction

In fact, for any defined unqualified name x the assignment "n = x"
guarantees that "n is x" is True.

> True <--- Ok, this seems reasonable. Nevertheless, I suspect I
> shouldn't quite rely on it.
>

You can take that to the bank on any working Python implementation. It's
hardwired into the language's semantics.

>>>> class MyClass(object):
> ... def myMethod(self):
> ... pass
> ...
>>>> c = MyClass()
>>>> m = c.myMethod
>>>> m is c.myMethod
> False <--- What? Why is that?
>
> In my mind I was expecting that when the method is assigned to "m" all
> that it happens is that its address is assigned to the name "m" so
> that effectively the same address is now pointed to by two names, like
> in the function case. I googled around for some hint but I wouldn't
> exactly say I'm clear on the issue just yet...
>
> Can anybody shed some light? Or point to a resource to look at? Or
> what's the bit of python's source code that is responsible for dealing
> with those assignments?
>

Instance-relative references to class methods are a very special case.
They become what are called "bound methods" - the interpreter creates a
new bound method for each reference.

This allows the bound method to provide the instance as a first argument
when it is called.

>>> class C(object):
... def MyMethod(self):
... pass
...
>>> c = C()
>>> a = c.MyMethod
>>> b = c.MyMethod
>>> a, b
(<bound method C.MyMethod of <__main__.C object at 0x7ff33fcc>>, <bound
method C.MyMethod of <__main__.C object at 0x7ff33fcc>>)
>>>

regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
Holden Web LLC http://www.holdenweb.com/
Want to know? Come to PyCon - soon! http://us.pycon.org/

Steve Holden

unread,
Mar 20, 2009, 2:48:41 PM3/20/09
to pytho...@python.org, pytho...@python.org
Emanuele D'Arrigo wrote:
> Hi everybody,
>
> I was unit testing some code today and I eventually stumbled on one of
> those "is" issues quickly solved replacing the "is" with "==". Still,
> I don't quite see the sense of why these two cases are different:
>
>>>> def aFunction():
> ... pass
> ...
>>>> f = aFunction
>>>> f is aFunction

In fact, for any defined unqualified name x the assignment "n = x"


guarantees that "n is x" is True.

> True <--- Ok, this seems reasonable. Nevertheless, I suspect I


> shouldn't quite rely on it.
>

You can take that to the bank on any working Python implementation. It's
hardwired into the language's semantics.

>>>> class MyClass(object):


> ... def myMethod(self):
> ... pass
> ...
>>>> c = MyClass()
>>>> m = c.myMethod
>>>> m is c.myMethod
> False <--- What? Why is that?
>
> In my mind I was expecting that when the method is assigned to "m" all
> that it happens is that its address is assigned to the name "m" so
> that effectively the same address is now pointed to by two names, like
> in the function case. I googled around for some hint but I wouldn't
> exactly say I'm clear on the issue just yet...
>
> Can anybody shed some light? Or point to a resource to look at? Or
> what's the bit of python's source code that is responsible for dealing
> with those assignments?
>

Instance-relative references to class methods are a very special case.
They become what are called "bound methods" - the interpreter creates a
new bound method for each reference.

This allows the bound method to provide the instance as a first argument
when it is called.

>>> class C(object):
... def MyMethod(self):

... pass
...

Terry Reedy

unread,
Mar 20, 2009, 6:09:48 PM3/20/09
to pytho...@python.org
Emanuele D'Arrigo wrote:
> Hi everybody,
>
> I was unit testing some code today and I eventually stumbled on one of
> those "is" issues quickly solved replacing the "is" with "==". Still,
> I don't quite see the sense of why these two cases are different:
>
>>>> def aFunction():
> ... pass
> ...
>>>> f = aFunction
>>>> f is aFunction
> True <--- Ok, this seems reasonable. Nevertheless, I suspect I
> shouldn't quite rely on it.
>
>>>> class MyClass(object):
> ... def myMethod(self):
> ... pass
> ...
>>>> c = MyClass()
>>>> m = c.myMethod
>>>> m is c.myMethod
> False <--- What? Why is that?

Compare that to MyClass.myMethod is MyClass.myMethod, which is True at
least in 3.0. Repeated attribute accesses may or may not return the
same object. Remember that class (and instance) attributes can be
computed properties, or produced by whatever means in __getattr__.

Also, x.a = b; x.a==b may or may not return True as the setting might be
intercepted by __setattr__.

tjr

Christian Heimes

unread,
Mar 20, 2009, 7:36:29 PM3/20/09
to pytho...@python.org
Terry Reedy wrote:

> Compare that to MyClass.myMethod is MyClass.myMethod, which is True at
> least in 3.0.

It's true because Python 3.0 has no unbound methods. MyClass.myMethod
returns the function object. It's possible to get the same behavior in 2.x:

MyClass.myMethod.im_func is MyClass.myMethod.im_func

Christian

Bruno Desthuilliers

unread,
Mar 21, 2009, 2:42:41 PM3/21/09
to
Emanuele D'Arrigo a écrit :

> Hi everybody,
>
> I was unit testing some code today and I eventually stumbled on one of
> those "is" issues quickly solved replacing the "is" with "==". Still,
> I don't quite see the sense of why these two cases are different:
>
>>>> def aFunction():
> ... pass
> ...
>>>> f = aFunction
>>>> f is aFunction
> True <--- Ok, this seems reasonable. Nevertheless, I suspect I
> shouldn't quite rely on it.

And you're wrong.

>>>> class MyClass(object):
> ... def myMethod(self):
> ... pass
> ...
>>>> c = MyClass()
>>>> m = c.myMethod
>>>> m is c.myMethod
> False <--- What? Why is that?

c.myMethod resolves to MyClass.__dict['myMethod'].__get__(c), which
returns a new Method object.

> In my mind I was expecting that when the method
> is assigned to "m" all
> that it happens is that its address is assigned to the name "m" so
> that effectively the same address is now pointed to by two names, like
> in the function case.

The "m = c.myMethod" statement effectively binds name "m" to a Method
instance. What you don't get is what really is a Method object.

You assume that the "def" statement behaves differently when used within
a "class" statement - which is just not the case. The "def" statement
_always_ create a function object. Just try this:

print type(MyClass.__dict__["myMethod"])
print type(MyClass.myMethod)
print type(c.myMethod)

What happens here is that the function type implements the descriptor
protocol in such a way to return a Method object (a callable object
wrapping the function and instance - IOW, a partial application of
MyClass.__dict__["myMethod"] and 'c') when resolved as a class
attribute. So each time you evaluate c.myMethod, you get a new Method
object.

> I googled around for some hint but I wouldn't
> exactly say I'm clear on the issue just yet...
>
> Can anybody shed some light? Or point to a resource to look at?

I lost track of how many times I explained this on this newsgroup - but
googling for +method +descriptor +"lookup rules" should yield some results.

> Or
> what's the bit of python's source code that is responsible for dealing
> with those assignments?

It has nothing to do with assignement - it's about attributes lookup rules.

"Martin v. Löwis"

unread,
Mar 21, 2009, 4:17:58 PM3/21/09
to Emanuele D'Arrigo
>>>> m is c.myMethod
> False <--- What? Why is that?

I think nobody has said this plainly yet (although Terry
points it out also): You cannot rely that

foo.bar is foo.bar

for any object foo and any attribute bar. In some cases,
that relation may hold, in other cases, it may not.
It depends on whether foo intercepts access to bar and
returns something different each time.

As others have explained: objects return something new
for every access to a method.

Regards,
Martin

Emanuele D'Arrigo

unread,
Mar 22, 2009, 6:48:28 PM3/22/09
to
Thank you all for the replies!

Manu

J. Cliff Dyer

unread,
Mar 24, 2009, 10:25:10 AM3/24/09
to Emanuele D'Arrigo, pytho...@python.org
On Fri, 2009-03-20 at 11:20 -0700, Emanuele D'Arrigo wrote:

So here's a f'rinstance counterexample for you:

class TempAttributeClass(object):
def __init__(self):
self.temp = True

def foo(self, x):
return len(x) + 1

def __getattribute__(self, attr):
attribute = object.__getattribute__(self,attr)
if hasattr(attribute, '__call__'):
if object.__getattribute__(self, 'temp'):
self.temp = False
return len
else:
return attribute
else:
return attribute

The first time a method is accessed from an instance of this class, it
will return len instead.

>>> print TempAttributeClass.foo
<unbound method TempAttributeClass.foo>
>>> c = TempAttributeClass()
>>> l = [1,2,3]
>>> x = c.foo
>>> x(l)
3
>>> c.foo
4
>>> x == c.foo
False
>>> print x
<built-in function len>
>>> print y
<bound method TempAttributeClass.foo of <__main__.TempAttributeClass
object at 0x7f672b35e290>>

c.foo is a bound attribute, but what has it been bound to? Well, I
guess it technically, it's bound to the instance c, but what has it been
bound from? That depends first on what it encounters when traversing
its base classes, and second on how it's accessing its attributes. As
the example above shows, python is too dynamic to make any guarantees
about any of that.

Another way you could mess with that is by changing the __class__
attribute on c.

class A(object):
x = 4
def __init__(self):
self.y = 5

class B(object):
x = u'cow'
def __init__(self):
self.y = u'goat'

>>> c = A()
>>> c.x
4
>>> c.y
5
>>> c.__class__ = B
>>> # Note that neither c nor x were changed in the last step
... c.x # Class attribute found on B now
u'cow'
>>> c.y # Instance attribute: already initialized from A.__init__
5
>>> c.__init__() # Reinitialize c, now using B.__init__
>>> c.y # Re-initialized instance attribute
u'goat'


Cheers,
Cliff

0 new messages