15

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?

1
  • 2
    A "bound method" is an instance method (by convention gets self as first argument), defined during class definition. If you assign a function with a method compatible parameter list (i.e. taking self as first argument) to an instance, this will not be a bound method of the class, but only be a attribute of that instance. Commented Aug 31, 2020 at 6:26

6 Answers 6

13
+100

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.

Sign up to request clarification or add additional context in comments.

Comments

4

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 class instance assigned to hc.

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.

4 Comments

I apologize for the inconvenience but I'm really struggling with the verbiage; when I was reading the docs it seemed promising to use 'logic' notation; in hindsight perhaps you're right; anyway, thanks for the explanation, it makes sense
No worries - I know what it's like when you learn that stuff, but it's my experience that 99% of people who learn it forget all about it again and as such, it's not very helpful in most cases outside scientific Computer Science or Logic papers. Note that @avivyaniv provides a nice addition, showing a way you can set a user-defined function as a bound method - I only focused on what the text you were asking about meant.
If 'def greet_with_hi(self, name):' is changed to 'def greet_with_hi(name):' it works.
@TomJ you're right that it works, in the sense that it doesn't generate an error, but of course you still wouldn't have access to the class instance in the body of .greet(), like you would for a bound method, which is what the quote in the question was about.
3

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()
# ()  

1 Comment

OK, I think it makes sense; in the case of the class method, isn't a.f() turned into f(A) or something like f(type(a))? if that's correct, why does a.f() print (<__main__.A object at ...) instead of <class '__main__.A'>?
2

I think the meaning is best clarified by way of example.

Suppose that we have a class instance containing various attributes that are user-defined functions.

  • add1 was added by defining it as part of the class definition.
  • add2 was added by monkey-patching the class before instantiation.
  • add3 was added by monkey-patching the class after instantiation
  • add4 was added by monkey-patching the instance after instantiation
class Number:

    def __init__(self, x):
        self.x = x

    def add1(self):
        return self.x + 1

def add2(self):
    return self.x + 2

def add3(self):
    return self.x + 3

def add4(self):
    return self.x + 4

setattr(Number, 'add2', add2)

two = Number(2)

setattr(Number, 'add3', add3)

setattr(two, 'add4', add4)

print(two.add1())  # prints 3
print(two.add2())  # prints 4
print(two.add3())  # prints 5
print(two.add4())  # TypeError: add4() missing 1 required positional argument: 'self'

We try calling these.

The first three all work (in the case of add3 it doesn't even matter that it wasn't an attribute of the class at the time of instantiation).

Note that when we call these, we do not explicitly pass anything corresponding to the first positional argument inside the function (i.e. self) -- it is added automatically for us. This is what is meant by it being a bound method. It is declared with one positional argument and we are explicitly passing none at all.

But in the case of add4 it is complaining about missing positional argument - this is because the instance is not automatically added as a first argument. (It would work if you used explicitly two.add4(two).)

1 Comment

is the only difference between setattr(Number, 'add2', add2) and setattr(Number, 'add3', add3) the time that they are defined relative to when two is instantiated?
1

Bound methods are based on descriptors which are documented here. There is even a section "Functions and Methods".

It is a known fact, that descriptors work only in the classes, not in instances. That is documented here.

Putting those two pieces of information together explains the difference quoted in the question:

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.

Comments

1

Bound methods python a bound-method is the 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.

Let’s understand this concept with an example:

# Python code to demonstrate 
# use of bound methods 
  
class A: 
  
    def func(self, arg): 
        self.arg = arg 
        print("Value of arg = ", arg) 
  
  
# Creating an instance 
obj = A()   
  
# bound method 
print(obj.func) 
Output:

< bound method A.func of <__main__.A object at 0x7fb81c5a09e8>>

Here,

obj.func(arg) is translated by python as A.func(obj, arg). The instance obj is automatically passed as the first argument to the function called and hence the first parameter of the function will be used to access the variables/functions of the object.

Let’s see another example of the Bound method.

# Python code to demonstrate 
# use of bound methods 
  
class Car: 
    # Car class created 
    gears = 5
  
    # a class method to change the number of gears  
    @classmethod
    def change_gears(cls, gears): 
        cls.gears = gears 
  
  
# instance of class Car created 
Car1 = Car() 
  
  
print("Car1 gears before calling change_gears() = ", Car1.gears) 
Car1.change_gears(6)  
print("Gears after calling change_gears() = ", Car1.gears) 
  
# bound method 
print(Car1.change_gears) 
Output:

Car1 gears before calling change_gears() =  5
Gears after calling change_gears() =  6
<bound method Car.change_gears of <class '__main__.Car'>>

The above code is an example of a class method. A class method is like a bound method except that the class of the instance is passed as an argument rather than the instance itself. Here in the above example when we call Car1.change_gears(6), the class ‘Car’ is passed as the first argument.

Need for these bound methods The methods inside the classes would take at least one argument. To make them zero-argument methods, ‘decorators‘ has to be used. Different instances of a class have different values associated with them.

For example, if there is a class “Fruits”, and instances like apple, orange, mango are possible. Each instance may have a different size, color, taste, and nutrients in it. Thus to alter any value for a specific instance, the method must have ‘self’ as an argument that allows it to alter only its property.

Example:

class sample(object): 
  
    # Static variable for object number 
    objectNo = 0
  
    def __init__(self, name1): 
  
        # variable to hold name 
        self.name = name1 
  
        # Increment static variable for each object 
        sample.objectNo = sample.objectNo + 1
  
        # each object's unique number that can be 
        # considered as ID 
        self.objNumber = sample.objectNo 
  
    def myFunc(self): 
        print("My name is ", self.name,  
              "from object ", self.objNumber) 
  
    def alterIt(self, newName): 
        self.name = newName 
  
    def myFunc2(): 
        print("I am not a bound method !!!") 
  
  
# creating first instance of class sample         
samp1 = sample("A") 
samp1.myFunc() 
  
# unhide the line below to see the error 
# samp1.myFunc2() #----------> error line 
  
  
# creating second instance of class sample     
samp2 = sample("B") 
samp2.myFunc() 
samp2.alterIt("C") 
samp2.myFunc() 
samp1.myFunc() 
Output:

My name is  A from object  1
My name is  B from object  2
My name is  C from object  2
My name is  A from object  1

In the above example two instances namely samp1 and samp2 are created. Note that when the function alterIt() is applied to the second instance, only that particular instance’s value is changed. The line samp1.myFunc() will be expanded as sample.myFunc(samp1). For this method, no explicit argument is required to be passed. The instance samp1 will be passed as an argument to the myFunc(). The line samp1.myFunc2() will generate the error :

Traceback (most recent call last):
  File "/home/4f130d34a1a72402e0d26bab554c2cf6.py", line 26, in 
    samp1.myFunc2() #----------> error line
TypeError: myFunc2() takes 0 positional arguments but 1 was given

It means that this method is unbound. It does not accept any instance as an argument. These functions are unbound functions.

Sources: Geeks For Geeks: Bound Methods Python

Geeks For Geeks: Bound, unbound and static methods in Python

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.