Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does automatic currying with self when assigning a method into a var work in Python 3?

I am writing a context manager to wrap the builtins.print function. And this works fine. However I encountered a Python behaviour that I can't wrap my head around:

Whenever a classes' method is assigned into a variable for later calling, the first "self" argument seems to be automatically stored as well and used for all later calls.

Here's an example illustrating the point:

import functools

class Wrapper:
    def wrap(self):
        return self._wrapped   #functools.partial(self._wrapped, self)

    def _wrapped(self, *args, **kwargs):
        print('WRAPPED!', *args, **kwargs)
        print('..knows about self:', self)

wrapped = Wrapper().wrap()
wrapped('expect self here', 'but', 'get', 'all', 'output')

The output:

WRAPPED! expect self here but get all output
..knows about self: <__main__.Wrapper object at 0x2aaaab2d9f50>

Of course for normal functions (outside of classes) this magic does not happen. I can even assign that method in the example above directly without going through instantiation:

wrapped = Wrapper._wrapped
wrapped('expect self here', 'but', 'get', 'all', 'output')

And now I get what I first expected:

WRAPPED! but get all output
..knows about self: expect self here

In my original code, I used the functools.partial to curry-in the self, but then discovered that this is not even required.

I like the current behaviour, but I'm not yet understanding the reasoning with respect to consistency and "being obvious".

I'm working with Python 3.1.2 here.

Is this question with the answer to use types.MethodType related? Searching here and in the 'net largely results in basic info on currying/partial function calls and packing/unpacking of arg lists. Maybe I used inadequate search terms (e.g. "python currying methods".)

Can anyone shed some light into this behaviour?

Is this the same in Py2 and Py3?

like image 941
cfi Avatar asked Jun 04 '26 14:06

cfi


2 Answers

Whenever you take the method from an instance (as in return self._wrapped) then self will be remembered.

Whenever you take the method from a class (as in Wrapper._wrapped) then self is not (cannot be) remembered.

As an example, try this:

upper = 'hello'.upper
print(upper())

upper = str.upper
print(upper())

You'll see HELLO, followed by TypeError: descriptor 'upper' of 'str' object needs an argument

like image 86
Ethan Furman Avatar answered Jun 07 '26 10:06

Ethan Furman


When an instance method is called, that call will automatically pass in the instance as the first parameter. This is what happens here.

When you do

return self._wrapped

You will return an instance method. Calling it will pass in the instance as the first parameter, that is self. But in the second case you call the method on the class, and hence there exists no instance to get passed in, so no instance gets passed in.

The "storage" of this is simply that instance methods know which instance they belong to. If you don't want that behavior return the unbound class method instead.

class Wrapper:
    def wrap(self):
        return Wrapper._wrapped

    def _wrapped(self, *args, **kwargs):
        print('WRAPPED!', *args, **kwargs)
        print('..knows about self:', self)
like image 35
Lennart Regebro Avatar answered Jun 07 '26 10:06

Lennart Regebro



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!