TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead)

Steven D'Aprano steve+comp.lang.python at pearwood.info
Thu Jul 7 14:36:33 CEST 2011


Kannan Varadhan wrote:

> Hi Folks,
> 
> Here is something I don't fully understand.
> 
>   1 def DefaultTracer(fmt, *args):
>   2   fmt = 'in DefaultTracer ' + fmt
>   3   print(fmt % args)
>   4   pass
>   5 def myTracer(fmt, *args):
>   6   fmt = 'in myTracer ' + fmt
>   7   print(fmt % args)

Please don't post code with line numbers. That makes it difficult to copy
and paste your function into an interactive session, so that we can run it
and see what it does.

[...]
> I want ClassDefaultTracer to store a reference to DefaultTracer and be
> used by instances of MyClass.  Why does line20 give me the following
> error:
> 
> $ python foo.py
> in DefaultTracer Testing at first
> in myTracer Testing at first
> Traceback (most recent call last):
>   File "foo.py", line 20, in <module>
>     MyClass().trace('Using ClassDefaultTracer')
>   File "foo.py", line 14, in trace
>     self.InstanceTracer(fmt, *args)
> TypeError: unbound method DefaultTracer() must be called with MyClass
> instance as first argument (got str instance instead)

Unfortunately you've run into a side-effect of one of the very few "Do What
I Mean" aspects of Python: under many circumstances, storing a function in
a class automatically turns it into a method. This is almost always what
you want, but not this time.

Here's a simple example:

>>> def function(s):
...     print s
...
>>> class C(object):
...     func = function
...     print func, type(func)
...
<function function at 0xb7f892cc> <type 'function'>
>>> print C.func, type(C.func)
<unbound method C.function> <type 'instancemethod'>

How to interpret this:

The class block is an executable statement which is executed at runtime.
During that execution, "print func" is called, and it sees func is an
actual function object.

After the block finishes executing, a class object (also called a type) is
created. This is where the DWIM behaviour happens: function attributes are
automatically turned into methods. That's normally what you want, but in
this case, it gives you the surprising result:

>>> C.func("hello world")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method function() must be called with C instance as first
argument (got str instance instead)


Instead of calling it from the class C, you can call it from an instance:

>>> c = C()
>>> c.func("hello world")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function() takes exactly 1 argument (2 given)


Two arguments? What? Where's the second argument???

But wait: *bound* methods automatically get the instance as their first
argument. (When you define a method in the class, you write a function
with "self" as the first argument.) A bound method is one that is called
from an instance, not the class, and it gets the instance as first argument
automatically:

instance.method(args)  # a bound method

is translated by Python into:

theclass = type(instance)
theclass.method(instance, args)  # an unbound method

So if we do this, the method works:

>>> c.func()  # bound methods get the instance as first argument
<__main__.C object at 0xb7f8fb6c>



There are a couple of solutions to avoid this. One is to live with it:


def function(self, s):  # Define this as if it were a method.
    print s

class C(object):
    func = function


Now you can call:

instance = C()
# unbound method, you are responsible for supplying "self"
C.func(instance, arg)

# bound method, Python supplies "self"
instance.func(arg)


but of course now you can't call function(arg) from outside the class.

Another solution is to use a staticmethod:

def function(s):
    print s

class C(object):
    func = staticmethod(function)



A third is to disguise the function:

class C(object):
    func = (function,)  # In a tuple

and then call C.func[0](arg)

but that's hideous. Don't do that.


Fourthly, the DWIM magic only happens when you store a function in the
*class*, not if you do it in the instance:


class C(object):
    def __init__(self):
        self.func = function


but now you can't call C.func because it doesn't exist, you have to
initialise an instance first.


> Alternately, how can I achieve what I want, i.e. a class-wide default
> used by all instances created off it, but
> itself be changeable, so that once changed, the changed default would
> be used by subsequent newer instances of that class.


class C(object):
    default = staticmethod(function)
    def __init__(self):
        self.func = type(self).default


ought to do it.




-- 
Steven




More information about the Python-list mailing list