Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #76177 > unrolled thread

odd difference calling function from class or instance variable

Started byGregS <not@my.real.address.com>
First post2014-08-13 10:06 +0100
Last post2014-08-13 20:29 +1000
Articles 5 — 3 participants

Back to article view | Back to comp.lang.python


Contents

  odd difference calling function from class or instance variable GregS <not@my.real.address.com> - 2014-08-13 10:06 +0100
    Re: odd difference calling function from class or instance variable Peter Otten <__peter__@web.de> - 2014-08-13 11:40 +0200
    Re: odd difference calling function from class or instance variable Chris Angelico <rosuav@gmail.com> - 2014-08-13 19:45 +1000
    Re: odd difference calling function from class or instance variable GregS <not@my.real.address.com> - 2014-08-13 11:20 +0100
      Re: odd difference calling function from class or instance variable Chris Angelico <rosuav@gmail.com> - 2014-08-13 20:29 +1000

#76177 — odd difference calling function from class or instance variable

FromGregS <not@my.real.address.com>
Date2014-08-13 10:06 +0100
Subjectodd difference calling function from class or instance variable
Message-ID<201408131006549222-not@myrealaddresscom>
Hello,

This is my first post here so please gently inform me of any etiquette 
breaches.

I'm seeing a behaviour I can't explain with Python 3.4.1 when I call a 
function via a reference stored in an object.

When I assign the reference as a class variable, the reference has 
__self__ set, too, so I get an extra argument passed to the function.  
If I assign the reference as an instance variable, then __self__ is 
unset so no extra argument.

Here's what I mean:

>>> def print_args(*args):
	print(args)
	
>>> class C:
	ref = None
	
>>> C.ref = print_args    # assign to class variable
>>> i = C()
>>> i.ref()     # call via class variable - get a 'self' argument passed
(<__main__.C object at 0x1071a05f8>,)
>>> i.ref = print_args   # assign to instance variable
>>> i.ref()     # call via instance variable: no arguments
()

If you look at i.ref.__self__ for the two cases, you'll see what's 
going on.  I've tried RTFMing but can't find the reason for the two 
behaviours.  Could someone provide an explanation for me, please?

Thanks,

Greg

[toc] | [next] | [standalone]


#76179

FromPeter Otten <__peter__@web.de>
Date2014-08-13 11:40 +0200
Message-ID<mailman.12913.1407922838.18130.python-list@python.org>
In reply to#76177
GregS wrote:

> Hello,
> 
> This is my first post here so please gently inform me of any etiquette
> breaches.
> 
> I'm seeing a behaviour I can't explain with Python 3.4.1 when I call a
> function via a reference stored in an object.
> 
> When I assign the reference as a class variable, the reference has
> __self__ set, too, so I get an extra argument passed to the function.
> If I assign the reference as an instance variable, then __self__ is
> unset so no extra argument.
> 
> Here's what I mean:
> 
>>>> def print_args(*args):
> print(args)
> 
>>>> class C:
> ref = None
> 
>>>> C.ref = print_args    # assign to class variable
>>>> i = C()
>>>> i.ref()     # call via class variable - get a 'self' argument passed
> (<__main__.C object at 0x1071a05f8>,)
>>>> i.ref = print_args   # assign to instance variable
>>>> i.ref()     # call via instance variable: no arguments
> ()
> 
> If you look at i.ref.__self__ for the two cases, you'll see what's
> going on.  I've tried RTFMing but can't find the reason for the two
> behaviours.  Could someone provide an explanation for me, please?

When an attribute is found in the instance it is left as-is, so

i.ref()

is the same as

print_ref()

When the attribute is found in the class and itself has a __get__ attribute 

i.ref()

is equivalent to

print_ref.__get__(i, C)()

which creates a bound method object (i. e. it is assumed that the function  
implements a method):

>>> class C: pass
... 
>>> def f(self): pass
... 
>>> f.__get__(C(), C)
<bound method C.f of <__main__.C object at 0x7f3a99ce86a0>>

As you have seen a bound method implicitly passes the instance as the first 
arg to the function. The underlying mechanism is called "descriptor 
protocol" and is also used to implement properties.

If you need to store a function in the class you can wrap it as a 
staticmethod:

>>> def print_args(*args): print(args)
... 
>>> class C:
...     ref = staticmethod(print_args)
... 
>>> C().ref()
()

[toc] | [prev] | [next] | [standalone]


#76180

FromChris Angelico <rosuav@gmail.com>
Date2014-08-13 19:45 +1000
Message-ID<mailman.12914.1407923108.18130.python-list@python.org>
In reply to#76177
On Wed, Aug 13, 2014 at 7:06 PM, GregS <not@my.real.address.com> wrote:
> If you look at i.ref.__self__ for the two cases, you'll see what's going on.
> I've tried RTFMing but can't find the reason for the two behaviours.  Could
> someone provide an explanation for me, please?

What you're seeing there is the magic of instance methods. I'll
simplify it some by defining the method right there in the class,
rather than doing the weird injection that you were doing:

>>> class C:
    def meth(self):
        print("Hi! I'm a method.",self)

>>> C().meth()
Hi! I'm a method. <__main__.C object at 0x012BC6D0>
>>> C.meth
<function C.meth at 0x012AF300>
>>> C().meth
<bound method C.meth of <__main__.C object at 0x012AEDF0>>
>>> _()
Hi! I'm a method. <__main__.C object at 0x012AEDF0>

When you look up something on the instance, if there's a regular
function of that name on its class, you'll get back a piece of magic
called a bound method. It's a curried function, if you know what that
means (if you don't, just skip this sentence). When you then call that
bound method, it ultimately goes back to the original function, with a
pre-filled first argument (which comes from __self__).

Basically, what this means is that a bound method can be treated like
a function, and it automatically keeps track of its proper state; the
unbound method *is* a function, so if you call that directly, you'll
need to pass it an object as self.

>>> C.meth(C())
Hi! I'm a method. <__main__.C object at 0x0169AA70>

As a general rule, though, you won't be doing this kind of thing.
Define functions inside a class body, and then call them on instances.
Everything'll happily work, and all these little details are magic :)

ChrisA

[toc] | [prev] | [next] | [standalone]


#76184

FromGregS <not@my.real.address.com>
Date2014-08-13 11:20 +0100
Message-ID<2014081311203037705-not@myrealaddresscom>
In reply to#76177
Thanks to both of you for your incredibly prompt replies.  My homework 
for tonight is to digest the descriptor protocol...

Peter, thanks for suggesting using staticmethod() to get the behaviour 
I was expecting.  I've only used staticmethod as a decorator before now.

Chris, I agree that it's not every day you assign functions to class 
attributes, but it does have its uses (I won't bore you with mine).  
Now that I know how it treads on the toes of Python's method magic, I 
can decide whether it's the best approach or not.

Thanks again,

Greg


[toc] | [prev] | [next] | [standalone]


#76185

FromChris Angelico <rosuav@gmail.com>
Date2014-08-13 20:29 +1000
Message-ID<mailman.12919.1407925761.18130.python-list@python.org>
In reply to#76184
On Wed, Aug 13, 2014 at 8:20 PM, GregS <not@my.real.address.com> wrote:
> Thanks to both of you for your incredibly prompt replies.  My homework for
> tonight is to digest the descriptor protocol...
>
> Peter, thanks for suggesting using staticmethod() to get the behaviour I was
> expecting.  I've only used staticmethod as a decorator before now.
>
> Chris, I agree that it's not every day you assign functions to class
> attributes, but it does have its uses (I won't bore you with mine).  Now
> that I know how it treads on the toes of Python's method magic, I can decide
> whether it's the best approach or not.

You seem to know what you're doing, which is a good start :) I aimed
my explanation a bit lower than your actual knowledge turns out to be,
so go ahead and do what you know you need to do. You're not treading
on Python's toes, here, but you're basically recreating some of what
Python normally does under the covers, so you'll need to actually
understand (instead of just treating as black-box magic) stuff like
the descriptor protocol.

ChrisA

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web