lament for the demise of unbound methods

Hi All, In Python 2, I can figure out whether I have a method or a function, and, more importantly, for an unbound method, I can figure out what class the method belongs to:
There doesn't appear to be any way in Python 3 to do this, which is a little surprising and frustrating... What am I missing here? Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On 04/07/2013 12:55, Ronald Oussoren wrote:
That doesn't seem helpful as a sensible way to get back to the class object:
globals()[MyClass.method.__qualname__.split('.')[0]] <class '__main__.MyClass'>
Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

2013/7/4 Chris Withers <chris@simplistix.co.uk>:
globals() can only be used if MyClass is in the same module. Otherwise, you a more complex function: --------------- import types def get_function_class(func): obj = func for name in func.__qualname__.split('.')[:-1]: if name == "<locals>": raise ValueError("you lose") if isinstance(obj, types.FunctionType): obj = func.__globals__[name] else: # get a method of a class, or a class defined in a child obj = getattr(obj, name) return obj --------------- Victor

Am 04.07.2013 13:21, schrieb Chris Withers:
I removed unbound methods almost six years ago: http://hg.python.org/cpython/rev/48af6375207e Christian

On 04/07/2013 12:59, Christian Heimes wrote:
Not disputing when it happened, more the why... ...the recommended change doesn't work, for obvious reasons:
The loss of the ability to figure out the class from an unbound method seems quite an annoying step back from an introspection point of view. cheers, Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On Thu, Jul 4, 2013 at 8:13 AM, Chris Withers <chris@simplistix.co.uk>wrote:
It's only annoying if you take the perspective that methods are somehow special compared to functions. With the removal of bound class methods that makes methods == functions that are an attribute on a class. And when you take that perspective it makes having anything special about methods seem wrong. It also makes adding a function to a class post-class creation make more sense since there is no difference technically.

On Jul 4, 2013, at 2:34 AM, Brett Cannon <brett@python.org> wrote:
The loss of the ability to figure out the class from an unbound method seems quite an annoying step back from an introspection point of view.
It's only annoying if you take the perspective that methods are somehow special compared to functions. With the removal of bound class methods that makes methods == functions that are an attribute on a class. And when you take that perspective it makes having anything special about methods seem wrong. It also makes adding a function to a class post-class creation make more sense since there is no difference technically.
Well said, Brett. This is a nice summary. Raymond

Hi Guido, I've bumped into this a couple of times. First time was when I wanted to know whether what I had was a classmethod, staticmethod or normal method here: https://github.com/Simplistix/testfixtures/blob/master/testfixtures/replace.... This resulted in having to trawl through __dict__ here: https://github.com/Simplistix/testfixtures/blob/master/testfixtures/resolve.... ...rather than just using getattr. I bumped into it again, yesterday, trying to add support for classes to this lightweight dependency injection framework I'm developing: https://github.com/Simplistix/mush/blob/master/tests/test_runner.py#L189 Here's my local copy of that test: https://gist.github.com/cjw296/db64279c69cdc0c5e112 The workaround I was playing with this morning is a wrapper so that I know I have a class method, although what I really want to write at this line is: https://gist.github.com/cjw296/db64279c69cdc0c5e112#file-gistfile1-txt-L40 runner = Runner(T0, C1.meth, C2.meth1, C2.meth2) ...but if I do that, how can the runner know that what it gets for its second argument is a class method of C1? (which is this case means that it should do C1().meth() rather than C1.meth()) cheers, Chris On 04/07/2013 17:25, Guido van Rossum wrote:
-- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

Thanks for the code pointers. So it's all about monkeypatching. :-) I have only a little sympathy, as there still seems to be a way to do this, it's just less convenient. Too bad. --Guido On Thu, Jul 4, 2013 at 9:42 AM, Chris Withers <chris@simplistix.co.uk>wrote:
-- --Guido van Rossum (python.org/~guido)

On 04/07/2013 18:00, Guido van Rossum wrote:
Thanks for the code pointers. So it's all about monkeypatching. :-)
Well, that's the testfixtures use case, but for mush it's about figuring out whether you need to instantiate a class before calling a callable. MyClass.a_method is a bit like a functools.partial in the mush case, if I can pass that object around and know what to do with it (which I can in Python 2) then I only have to pass that around. In Python3, I either have to pass around the class, the method and a flag to indicate that a class and method are being passed, or wrap my own unboundmethod equivalent, meaning mush users would have to write method(MyClass, 'a_method') under Python 3 when they can just write MyClass.a_method under Python 2.
I don't know that Victor's suggestion will actually work in all the cases that MyClass.a_method.im_class does :-S Chris
-- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On 4 Jul 2013, at 19:00, Guido van Rossum <guido@python.org> wrote:
Thanks for the code pointers. So it's all about monkeypatching. :-) I have only a little sympathy, as there still seems to be a way to do this, it's just less convenient. Too bad.
I've also lamented the death of bound methods in Python 3 for mock "autospeccing". Autospec introspects objects and provides mock objects with the same attributes - and with the same method signatures. For methods it needs to trim the first argument (because instances are called externally without self of course). Not being able to tell the difference between a module level function and an unbound method caused some pain then. (I worked round it by flowing the information about where the object came from through the code but it did add ugliness). Michael
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html

On 5 lip 2013, at 12:07, Martin v. Löwis <martin@v.loewis.de> wrote:
My guess is that Michael's design lets mock objects be introspected as well, i.e. they don't appear as magical as they really are to the user code. -- Best regards, Łukasz Langa WWW: http://lukasz.langa.pl/ Twitter: @llanga IRC: ambv on #python-dev

On 5 Jul 2013, at 12:26, Łukasz Langa <lukasz@langa.pl> wrote:
This is also true. Doing it up front has some conveniences - for example dir(...) works correctly. Michael
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html

On 6 Jul 2013 22:52, "Michael Foord" <fuzzyman@voidspace.org.uk> wrote:
This is also true. Doing it up front has some conveniences - for example
well, i.e. they don't appear as magical as they really are to the user code. dir(...) works correctly. Doesn't this really mean that in Py3, introspection APIs need a class *and* an instance of that class for robust analysis of expected descriptor results? (which was technically always true, the removal of unbound methods just makes it significantly more important to handle descriptors with no special behaviour on the class side of things). And yes, I'm aware that idea poses a significant challenge for correct handling of ABCs :P Cheers, Nick.
http://mail.python.org/mailman/options/python-dev/ncoghlan%40gmail.com

On 5 Jul 2013, at 12:07, "Martin v. Löwis" <martin@v.loewis.de> wrote:
How does that solve the problem? Given a call and a reference to the original "function object" I need to know whether or not to trim the first argument from the original signature or not (remove self if the corresponding function object is actually a method). Michael
Regards, Martin
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html

On 05/07/2013 11:26, "Martin v. Löwis" wrote:
Okay, but with this line: found = found.__getattribute__(found, n) I get a tonne of failures like this: File "<doctest testfixtures.tests.test_replacer.TestReplacer.test_with_statement[3]>", line 2, in <module> r.replace('testfixtures.tests.sample1.z',test_z) File "/Users/chris/LocalGIT/testfixtures/testfixtures/replace.py", line 50, in replace container, method, attribute, t_obj = resolve(target) File "/Users/chris/LocalGIT/testfixtures/testfixtures/resolve.py", line 17, in resolve found = found.__getattribute__(found, n) TypeError: expected 1 arguments, got 2 If I change it to : found = found.__getattribute__(n) I get fewer failures, but still plenty, now of the form: File "<doctest testfixtures.tests.test_replacer.TestReplacer.test_multiple_replace[7]>", line 1, in <module> r.replace('testfixtures.tests.sample1.X.y',test_y) File "/Users/chris/LocalGIT/testfixtures/testfixtures/replace.py", line 50, in replace container, method, attribute, t_obj = resolve(target) File "/Users/chris/LocalGIT/testfixtures/testfixtures/resolve.py", line 17, in resolve found = found.__getattribute__(n) TypeError: expected 1 arguments, got 0 ...so I'm back to: found = found.__dict__[n] ...and having to catch both KeyError and AttributeError. Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On 04/07/2013 20:50, Benjamin Peterson wrote:
Right, not to mention the fact that there's no way a library can ensure it monkeypatches that before the users of the library have created any classes. cheers, Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On 04/07/2013 12:55, Ronald Oussoren wrote:
That doesn't seem helpful as a sensible way to get back to the class object:
globals()[MyClass.method.__qualname__.split('.')[0]] <class '__main__.MyClass'>
Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

2013/7/4 Chris Withers <chris@simplistix.co.uk>:
globals() can only be used if MyClass is in the same module. Otherwise, you a more complex function: --------------- import types def get_function_class(func): obj = func for name in func.__qualname__.split('.')[:-1]: if name == "<locals>": raise ValueError("you lose") if isinstance(obj, types.FunctionType): obj = func.__globals__[name] else: # get a method of a class, or a class defined in a child obj = getattr(obj, name) return obj --------------- Victor

Am 04.07.2013 13:21, schrieb Chris Withers:
I removed unbound methods almost six years ago: http://hg.python.org/cpython/rev/48af6375207e Christian

On 04/07/2013 12:59, Christian Heimes wrote:
Not disputing when it happened, more the why... ...the recommended change doesn't work, for obvious reasons:
The loss of the ability to figure out the class from an unbound method seems quite an annoying step back from an introspection point of view. cheers, Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On Thu, Jul 4, 2013 at 8:13 AM, Chris Withers <chris@simplistix.co.uk>wrote:
It's only annoying if you take the perspective that methods are somehow special compared to functions. With the removal of bound class methods that makes methods == functions that are an attribute on a class. And when you take that perspective it makes having anything special about methods seem wrong. It also makes adding a function to a class post-class creation make more sense since there is no difference technically.

On Jul 4, 2013, at 2:34 AM, Brett Cannon <brett@python.org> wrote:
The loss of the ability to figure out the class from an unbound method seems quite an annoying step back from an introspection point of view.
It's only annoying if you take the perspective that methods are somehow special compared to functions. With the removal of bound class methods that makes methods == functions that are an attribute on a class. And when you take that perspective it makes having anything special about methods seem wrong. It also makes adding a function to a class post-class creation make more sense since there is no difference technically.
Well said, Brett. This is a nice summary. Raymond

Hi Guido, I've bumped into this a couple of times. First time was when I wanted to know whether what I had was a classmethod, staticmethod or normal method here: https://github.com/Simplistix/testfixtures/blob/master/testfixtures/replace.... This resulted in having to trawl through __dict__ here: https://github.com/Simplistix/testfixtures/blob/master/testfixtures/resolve.... ...rather than just using getattr. I bumped into it again, yesterday, trying to add support for classes to this lightweight dependency injection framework I'm developing: https://github.com/Simplistix/mush/blob/master/tests/test_runner.py#L189 Here's my local copy of that test: https://gist.github.com/cjw296/db64279c69cdc0c5e112 The workaround I was playing with this morning is a wrapper so that I know I have a class method, although what I really want to write at this line is: https://gist.github.com/cjw296/db64279c69cdc0c5e112#file-gistfile1-txt-L40 runner = Runner(T0, C1.meth, C2.meth1, C2.meth2) ...but if I do that, how can the runner know that what it gets for its second argument is a class method of C1? (which is this case means that it should do C1().meth() rather than C1.meth()) cheers, Chris On 04/07/2013 17:25, Guido van Rossum wrote:
-- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

Thanks for the code pointers. So it's all about monkeypatching. :-) I have only a little sympathy, as there still seems to be a way to do this, it's just less convenient. Too bad. --Guido On Thu, Jul 4, 2013 at 9:42 AM, Chris Withers <chris@simplistix.co.uk>wrote:
-- --Guido van Rossum (python.org/~guido)

On 04/07/2013 18:00, Guido van Rossum wrote:
Thanks for the code pointers. So it's all about monkeypatching. :-)
Well, that's the testfixtures use case, but for mush it's about figuring out whether you need to instantiate a class before calling a callable. MyClass.a_method is a bit like a functools.partial in the mush case, if I can pass that object around and know what to do with it (which I can in Python 2) then I only have to pass that around. In Python3, I either have to pass around the class, the method and a flag to indicate that a class and method are being passed, or wrap my own unboundmethod equivalent, meaning mush users would have to write method(MyClass, 'a_method') under Python 3 when they can just write MyClass.a_method under Python 2.
I don't know that Victor's suggestion will actually work in all the cases that MyClass.a_method.im_class does :-S Chris
-- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On 4 Jul 2013, at 19:00, Guido van Rossum <guido@python.org> wrote:
Thanks for the code pointers. So it's all about monkeypatching. :-) I have only a little sympathy, as there still seems to be a way to do this, it's just less convenient. Too bad.
I've also lamented the death of bound methods in Python 3 for mock "autospeccing". Autospec introspects objects and provides mock objects with the same attributes - and with the same method signatures. For methods it needs to trim the first argument (because instances are called externally without self of course). Not being able to tell the difference between a module level function and an unbound method caused some pain then. (I worked round it by flowing the information about where the object came from through the code but it did add ugliness). Michael
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html

On 5 lip 2013, at 12:07, Martin v. Löwis <martin@v.loewis.de> wrote:
My guess is that Michael's design lets mock objects be introspected as well, i.e. they don't appear as magical as they really are to the user code. -- Best regards, Łukasz Langa WWW: http://lukasz.langa.pl/ Twitter: @llanga IRC: ambv on #python-dev

On 5 Jul 2013, at 12:26, Łukasz Langa <lukasz@langa.pl> wrote:
This is also true. Doing it up front has some conveniences - for example dir(...) works correctly. Michael
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html

On 6 Jul 2013 22:52, "Michael Foord" <fuzzyman@voidspace.org.uk> wrote:
This is also true. Doing it up front has some conveniences - for example
well, i.e. they don't appear as magical as they really are to the user code. dir(...) works correctly. Doesn't this really mean that in Py3, introspection APIs need a class *and* an instance of that class for robust analysis of expected descriptor results? (which was technically always true, the removal of unbound methods just makes it significantly more important to handle descriptors with no special behaviour on the class side of things). And yes, I'm aware that idea poses a significant challenge for correct handling of ABCs :P Cheers, Nick.
http://mail.python.org/mailman/options/python-dev/ncoghlan%40gmail.com

On 5 Jul 2013, at 12:07, "Martin v. Löwis" <martin@v.loewis.de> wrote:
How does that solve the problem? Given a call and a reference to the original "function object" I need to know whether or not to trim the first argument from the original signature or not (remove self if the corresponding function object is actually a method). Michael
Regards, Martin
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html

On 05/07/2013 11:26, "Martin v. Löwis" wrote:
Okay, but with this line: found = found.__getattribute__(found, n) I get a tonne of failures like this: File "<doctest testfixtures.tests.test_replacer.TestReplacer.test_with_statement[3]>", line 2, in <module> r.replace('testfixtures.tests.sample1.z',test_z) File "/Users/chris/LocalGIT/testfixtures/testfixtures/replace.py", line 50, in replace container, method, attribute, t_obj = resolve(target) File "/Users/chris/LocalGIT/testfixtures/testfixtures/resolve.py", line 17, in resolve found = found.__getattribute__(found, n) TypeError: expected 1 arguments, got 2 If I change it to : found = found.__getattribute__(n) I get fewer failures, but still plenty, now of the form: File "<doctest testfixtures.tests.test_replacer.TestReplacer.test_multiple_replace[7]>", line 1, in <module> r.replace('testfixtures.tests.sample1.X.y',test_y) File "/Users/chris/LocalGIT/testfixtures/testfixtures/replace.py", line 50, in replace container, method, attribute, t_obj = resolve(target) File "/Users/chris/LocalGIT/testfixtures/testfixtures/resolve.py", line 17, in resolve found = found.__getattribute__(n) TypeError: expected 1 arguments, got 0 ...so I'm back to: found = found.__dict__[n] ...and having to catch both KeyError and AttributeError. Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk

On 04/07/2013 20:50, Benjamin Peterson wrote:
Right, not to mention the fact that there's no way a library can ensure it monkeypatches that before the users of the library have created any classes. cheers, Chris -- Simplistix - Content Management, Batch Processing & Python Consulting - http://www.simplistix.co.uk
participants (13)
-
"Martin v. Löwis"
-
Benjamin Peterson
-
Brett Cannon
-
Chris Withers
-
Christian Heimes
-
Eric Snow
-
Guido van Rossum
-
Michael Foord
-
Nick Coghlan
-
Raymond Hettinger
-
Ronald Oussoren
-
Victor Stinner
-
Łukasz Langa