There are several things that we should discuss here. I will try to explain different scenarios to give you an overview to see what's going on.
First let's see your last line:
my_obj.first_func(a, b) + my_obj.second_func(a, b)
You are trying to get the first_func attribute from the instance my_obj. Does it(or it's class) have that? No.
first_func is a variable in your global module. my_obj doesn't know anything about it.
So your exception is raised because of this...
Now considering your methods:
first and second methods are regular methods inside your class. When you say MyClass.first, You are getting a function object back not a method. This function accepts two parameters a and b.
But if you say my_obj.first_func you get a method object which has a bound instance object. Python fills the first parameter for you with a reference to the instance that you called this method from. Now your method object only accepts one parameter b. (This is how descriptors work)
With that being said, You have some options here:
1 - call them only from the class not the instance:
otherwise you will get exception because Python already filled the first parameter with the instance object.
class MyClass:
def first(a, b):
return a + b
def second(a, b):
return a * b
first_func = MyClass.first
second_func = MyClass.second
print(first_func(2, 3) + first_func(2, 3))
2- decorate them with staticmethod:
Now you can call either from instance or class, Python won't fill the first parameter for you.
class MyClass:
@staticmethod
def first(a, b):
return a + b
@staticmethod
def second(a, b):
return a * b
first_func = MyClass.first
second_func = MyClass.second
print(first_func(2, 3) + first_func(2, 3))
class MyClass:
@staticmethod
def first(a, b):
return a + b
@staticmethod
def second(a, b):
return a * b
my_obj = MyClass()
first_func = my_obj.first
second_func = my_obj.second
print(first_func(2, 3) + first_func(2, 3))
3- add a self parameter (it's name is not a rule but a strongly recommended convention):
This time if you call them from instance you don't need to pass the instance to the first parameter, but if you call them from the class you need to pass it as the first parameter.
class MyClass:
def first(self, a, b):
return a + b
def second(self, a, b):
return a * b
my_obj = MyClass()
first_func = my_obj.first
second_func = my_obj.second
print(first_func(2, 3) + first_func(2, 3))
class MyClass:
def first(self, a, b):
return a + b
def second(self, a, b):
return a * b
my_obj = MyClass()
first_func = MyClass.first
second_func = MyClass.second
print(first_func(my_obj, 2, 3) + first_func(my_obj, 2, 3))