Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is binding a class instance method different from binding a class method?

Tags:

python

I was reading the python docs and stumbled upon the following lines:

It is also important to note that user-defined functions which are attributes of a class instance are not converted to bound methods; this only happens when the function is an attribute of the class.

Please, someone explain what does that mean in plain english.

I'm going to introduce some shorthand notation:

let 'user-defined functions' be denoted by f,

let 'class instance' be denoted by ci while class denoted simply by c. Obviously(?), ci = c(), with some abuse of notation.

Also, allow membership statements to be recast in simple set notation eg 'user-defined functions which are attributes of a class instance' in shorthand is 'vf: fεa(ci)', where v: 'for all' and where 'a' is the shorthand for (set of) attributes (eg of a class or class instance) and 'ε' denotes the set membership function.

Also, the process of binding a function is described in shorthand by ci.f(*args) or c.f(*args) => f(ci, *args) or f(c, *args) (the former referring to an instance method call while the later referring to a class method call)

Using the newly introduced shorthand notation, does the quote from the docs imply that

vf: fεa(c), c.f(*args) => f(c, *args) is a true statement

while

vf: fεa(ci), ci.f(*args) => f(ci, *args) is false?

like image 387
yosimitsu kodanuri Avatar asked Aug 31 '20 06:08

yosimitsu kodanuri


3 Answers

Setting a User Defined Method to be an Attribute of Class, The Wrong Way

Consider the following example class A and function f:


class A:
    pass

def f(self):
    print("I\'m in user-defined function")

a = A()

The function f is defined separately and not inside the class.

Let's say you want to add function f to be an instance method for a object.

Adding it, by setting f as a attribute, won't work:

import types

class A:
    pass

def f(self):
    print("I\'m in user-defined function")

a = A()
a.f = f

# <function f at 0x000002D81F0DED30>
print(a.f)

# TypeError: f() missing 1 required positional argument: 'self'
# a.f()

Because function f is not bound to the object a.

That is why when calling a.f() it shall raise an error regarding the missing argument (if f has been bounded to a, that object a was the missing argument self).

This part is what the docs referred at:

It is also important to note that user-defined functions which are attributes of a class instance are not converted to bound methods.

Of course, all this has not to happen if function f has been defined inside class A, that's what the following part from the docs states:

...this only happens when the function is an attribute of the class.

Setting a User Defined Method to be an Attribute of Class, The Right Way

To add function f to object a you should use:

import types

class A:
    pass

def f(self):
    print("I\'m in user-defined function")

a = A()

a.f = types.MethodType( f, a )

# <bound method f of <__main__.A object at 0x000001EDE4768E20>>
print(a.f)

# Works! I'm in user-defined function
a.f()

Which bounds the user-defined method f to instance a.

like image 91
Aviv Yaniv Avatar answered Oct 17 '22 14:10

Aviv Yaniv


When you create a method the usual way, it will be a bound method: it receives the instance as first argument (which we usually assign to 'self'):

class A:
    def meth(*args):
        print(args)
        
        
a = A()
a.meth()
        
# (<__main__.A object at 0x7f56a137fd60>,)  

If you take an ordinary function and add it to the class attributes, it will work the same way:

def f(*args):
    print(args)
    
A.f = f
a = A()
a.f()
# (<__main__.A object at 0x7f56a137f700>,)

The instance gets passed as first argument, it is a bound method.

If, on the other side, you make the function an attribute of an instance of the class, it won't be a bound method = it won't be passed the instance as first argument when called:

a = A()
a.f = f
a.f()
# ()  
like image 3
Thierry Lathuille Avatar answered Oct 17 '22 14:10

Thierry Lathuille


I don't think the fancy-schmancy formal logic notation is helping here.

However, to answer the question: what does "user-defined functions which are attributes of a class instance are not converted to bound methods; this only happens when the function is an attribute of the class" mean?

A bound method is one which is dependent on the instance of the class as the first argument. It passes the instance as the first argument which is used to access the variables and functions. In Python 3 and newer versions of python, all functions in the class are by default bound methods.

So, if you create a user-defined function as an attribute of a class instance, it is not automatically converted to a bound method. 'Class instance' is just a Python way of saying what 'object' or 'object instance' means in other languages.

For example:

class HelloClass:
    greeting = 'Hello'

    def greet(self, name):
        print(f'{greeting} {name}')


hc = HelloClass()
hc.greet('John')

Here HelloClass is the class, while hc is the class instance. greet is a bound method, expecting at least a single parameter (called self by convention) which is automatically assigned the class instance when called - i.e. the value of self before printing hello John is the hc class instance.

Now, if you try this:

def greet_with_hi(self, name):
    print(f'Hi {name}')


class HiClass:
    greet = greet_with_hi


hc = HiClass()
hc.greet('John')

That works (although your IDE may object), but this doesn't work at all:

def greet_with_hi(self, name):
    print(f'Hi {name}')


class HiClass:
    def __init__(self):
        self.greet = greet_with_hi


hc = HiClass()
hc.greet('John')

It causes TypeError: greet_with_hi() missing 1 required positional argument: 'name'. And it should, because .greet on an instance of HiClass is not a bound method and the self greet_with_hi expects won't be filled automatically.

like image 3
Grismar Avatar answered Oct 17 '22 12:10

Grismar