Another of those "is" issues.

Discussion in 'Python' started by Emanuele D'Arrigo, Mar 20, 2009.

  1. 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
    Emanuele D'Arrigo, Mar 20, 2009
    #1
    1. Advertising

  2. 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.
    Benjamin Peterson, Mar 20, 2009
    #2
    1. Advertising

  3. Emanuele D'Arrigo

    Tim Wintle Guest

    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
    Tim Wintle, Mar 20, 2009
    #3
  4. Emanuele D'Arrigo

    Steve Holden Guest

    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, Mar 20, 2009
    #4
  5. Emanuele D'Arrigo

    Steve Holden Guest

    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, Mar 20, 2009
    #5
  6. Emanuele D'Arrigo

    Terry Reedy Guest

    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
    Terry Reedy, Mar 20, 2009
    #6
  7. 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.
    Bruno Desthuilliers, Mar 21, 2009
    #7
  8. >>>> 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
    Martin v. Löwis, Mar 21, 2009
    #8
  9. Thank you all for the replies!

    Manu
    Emanuele D'Arrigo, Mar 22, 2009
    #9
  10. On Fri, 2009-03-20 at 11:20 -0700, 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?
    >
    > 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
    >


    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
    J. Cliff Dyer, Mar 24, 2009
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Marcelo Birnbach [MS]
    Replies:
    0
    Views:
    3,306
    Marcelo Birnbach [MS]
    Jun 28, 2003
  2. Rushikesh Joshi
    Replies:
    0
    Views:
    427
    Rushikesh Joshi
    Aug 19, 2004
  3. BinnuChowdary
    Replies:
    1
    Views:
    520
    Swanand Mokashi
    May 1, 2006
  4. BinnuChowdary
    Replies:
    0
    Views:
    401
    BinnuChowdary
    May 2, 2006
  5. BinnuChowdary
    Replies:
    1
    Views:
    534
    =?UTF-8?B?R8O2cmFuIEFuZGVyc3Nvbg==?=
    May 2, 2006
Loading...

Share This Page