2

In the code below, class A has a member function which is set to point to a function defined outside the class. in class B, the same function is set to the external pointer in the class definition. Calling the function for an object of type A will fail, because the self does not get passed to the function. But for B, the self gets passed. Why does the self get passed for B, but not for A?

def f1(s,a):
    print s
    print a

class A(object):
    def __init__(self):
        self.fp1 = f1

class B(object):
    fp1 = f1

a=A()
b=B()
try:a.fp1("blaa")
except Exception, e: print `e` 
try:b.fp1("bluu")
except Exception, e: print `e` 

Output:

TypeError('f1() takes exactly 2 arguments (1 given)',)
<__main__.B object at 0x2ab0dacabed0>
bluu
Nutiu Lucian
  • 169
  • 2
  • 5

4 Answers4

4

When you did self.fp1 = f1 you just assigned a function to an instance variable of the class A. So when you call it you have to pass two arguments.

When you did:

   class B(object):
       fp1 = f1

during creation process of the class B python found a function fp1 in the class scope and created an instancemethod from it (replaced the variable with name fp1 with an instancemethod created from the function that it held before). When you call an instancemethod on an object self gets automatically passed as the first argument.

You can check this by typing:

>>> a = A()
>>> b = B()
>>> type(a.fp1)
function
>>> type(b.fp1)
instancemethod
Viktor Kerkez
  • 45,070
  • 12
  • 104
  • 85
4

In class A you bind a function to an instance. This could be really considered as "function pointer" so all arguments must be passed explicitly. In class B you bind the function to the class which will cause the function to work as method. You could modify class definition A to

class A(object):
    def __init__(self):
        A.fp1 = f1

which will give the same behavior has class B, i.e. fp1 of all instances points to f1, or you could wrap f1.

class A(object):
    def __init__(self):
        self.fp1 = lambda a: f1(self, a)

This will allow to change fp1 for each instance individually. The latter variant is probably what you were looking for.

dastrobu
  • 1,600
  • 1
  • 18
  • 32
1

The magic that makes instance.method(...) equivalent to Class.method(instance, ...) is dependent on the function object being an attribute of the class. The details vary (and with them, the ugly workaround by which you can create such a method). In Python 3, all functions are descriptors. In Python 2, there are special unbound method objects which are implicitly created to wrap functions stored as class attributes and do roughly what all functions do by themselves in Python 3.

In either case, accessing it through an instance creates a bound method which passes the instance along as first argument when called. In either case, a function accessed through an instance attribute is in no way special, it's just another object which can be passed around and used.

You can achieve similar behavior by either using partial (a bit of a leaky abstraction):

from functools import partial
# in __init__
self.fp1 = partial(f1, self)

or by creating a method which delegates:

def __init__(self):
    self._fp1 = f1

def fp1(*args, **kwds):
    return self._fp1(self, *args, **kwds)
0

In the first case you create a field in the class, that has a method object stored in it. So "a.fp1" is not a method call and therefore "a" is not put as the first argument. It's a retrieval of a method object, and then calling it.

For the second case, you can refer to the documentation:

Any function object that is a class attribute defines a method for instances of that class.

So, for b "fp1" becomes a method for instances of class b.

You can find more detailed explanation here: method objects vs function objects , Python class instances vs class

Community
  • 1
  • 1
BartoszKP
  • 34,786
  • 15
  • 102
  • 130