Function mistaken for a method

bruno at modulix onurb at xiludom.gro
Thu Jun 1 09:07:26 EDT 2006


Eric Brunel wrote:
> Hi all,
> 
> I just stepped on a thing that I can't explain. Here is some code
> showing  the problem:
> 
> -----------------------------
> class C:

Do yourself a favour : use new-style classes.
class C(object)

>   f = None
>   def __init__(self):
>     if self.f is not None:
>       self.x = self.f(0)
>     else:
>       self.x = 0
> 
> class C1(C):
>   f = int
> 
> class C2(C):
>   f = lambda x: x != 0
> 
> o1 = C1()
> print o1.x
> 
> o2 = C2()
> print o2.x
> -----------------------------
> 
> Basically, I want an optional variant function across sub-classes of
> the  same class. 
>
> I did it like in C1 for a start, then I needed
> something like  C2. The result is... surprising:
> 
> 0
> Traceback (most recent call last):
>   File "func-vs-meth.py", line 18, in ?
>     o2 = C2()
>   File "func-vs-meth.py", line 5, in __init__
>     self.x = self.f(0)
> TypeError: <lambda>() takes exactly 1 argument (2 given)

Not surprising at all.

Functions implement the descriptor protocol[1]. When bound to a class
and looked up via an instance, it's the __get__ method of the function
object that get called - with the instance as param, as defined by the
descriptor protocol. This method then return the function wrapped - with
the instance - in an Method object - which itself, when called, returns
the result of calling the function *with the instance as first
parameter*. Which is how methods can work on the instance, and why one
has to explicitly declare the instance parameter in "functions to be
used as methods", but not explicitly pass it at call time.

(please some guru correct me if I missed something here, but AFAIK it
must be a correct enough description of method invocation mechanism in
Python).

[1] about descriptors, see:
http://docs.python.org/ref/descriptors.html
http://www.geocities.com/foetsch/python/new_style_classes.htm#descriptors

> So the first works and o1.x is actually 0. 

int is not a function.
>>> type(int)
<type 'type'>

int is a type. A Python type is  a callable object, and act as a factory
for instances of it. If the type doesn't implement the descriptor
protocol, when bound to a class and looked up via an instance, normal
lookup rules apply. So the type object  is returned as is.


In your case, since int does'nt implement the descriptor protocol, once
looked up (and returned as is), it's called with a correct argument - so
everything runs fine.

Try this:

class Obj(object):
  def __new__(cls, val, *args, **kw):
    print "in Obj.__new__"
    print "- called with :"
    print "  cls :", cls
    print "  val :", val
    print "  args: %s" % str(args)
    print "  kw  : %s" % kw
    obj = object.__new__(cls, *args, **kw)
    print "got : %s - %s" % (obj, dir(obj))
    return obj

  def __init__(self, *args, **kw):
    print "in Obj.__init__"
    print "- called with :"
    print "  args: %s" % str(args)
    print "  kw  : %s" % kw


class C4(C):
  f = Obj

> But the second fails because 
> self is also being passed as the first argument to the lambda. 

Of course. It's a function, and it's bound to a class, and looked up via
an instance of the class.

Try this:

def truc(*args, **kw):
  print "in truc()__"
  print "- called with :"
  print "  args: %s" % str(args)
  print "  kw  : %s" % kw
  if len(args) > 1:
    return args[1]

class C6(C):
  f = truc


> Defining
> a  "real" function doesn't help: the error is the same.

What' a "real" function ?-) lambdas *are* real functions.
>>> type(lambda x: x)
<type 'function'>
>>>

> My actual question is: why does it work in one case and not in the
> other? 

cf above.

> As I see it, int is just a function with one parameter,

Nope, it's a type. Functions are just one kind of callable. Types are
callables too, as are any object overloading the call operator - which
is '()' - by implementing the __call__(self, ...) method.

class NotAFunc(object):
   def __call__(self):
      print "I'm not a function"
      return 42

func = NotAFunc()
func()

> and the
> lambda is  just another one. 

True. And functions implement the descriptor protocol.

> So why does the first work, and not the
> second? What  'black magic' takes place so that int is not mistaken for
> a method in the  first case?

cf above.

If you understood all my explanations, you now know how to solve the
problem.

Else, here the solution:

class C3(C):
  f = lambda self, x: return x


-- 
bruno desthuilliers
python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
p in 'onurb at xiludom.gro'.split('@')])"



More information about the Python-list mailing list