TypeError: unbound method DefaultTracer() must be called with MyClassinstance as first argument (got

  • Thread starter Kannan Varadhan
  • Start date

K

Kannan Varadhan

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)
8
9 class MyClass:
10 ClassDefaultTracer = DefaultTracer
11 def __init__(self):
12 self.InstanceTracer = MyClass.ClassDefaultTracer
13 def trace(self, fmt, *args):
14 self.InstanceTracer(fmt, *args)
15
16
17 DefaultTracer("Testing %s", 'at first')
18 myTracer("Testing %s", 'at first')
19
20 MyClass().trace('Using ClassDefaultTracer')
21
22 # override ClassDefaultTracer for all new instances
23 MyClass.ClassDefaultTracer = myTracer
24 MyClass().trace('Using Overridden ClassDefaultTracer')

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)

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.

Thanks,

Kannan
 
Ad

Advertisements

P

Peter Otten

Kannan said:
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)
8
9 class MyClass:
10 ClassDefaultTracer = DefaultTracer
11 def __init__(self):
12 self.InstanceTracer = MyClass.ClassDefaultTracer
13 def trace(self, fmt, *args):
14 self.InstanceTracer(fmt, *args)
15
16
17 DefaultTracer("Testing %s", 'at first')
18 myTracer("Testing %s", 'at first')
19
20 MyClass().trace('Using ClassDefaultTracer')
21
22 # override ClassDefaultTracer for all new instances
23 MyClass.ClassDefaultTracer = myTracer
24 MyClass().trace('Using Overridden ClassDefaultTracer')

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)

Functions written in Python are also "descriptors", they have a __get__()
method. The seemingly harmless

whatever = MyClass.ClassDefaultTracer

therefore results in something like

whatever = MyClass.__dict__["ClassDefaultTracer"].__get__(None, MyClass)

internally, and whatever becomes an "unbound method", essentially the
DefaultTracer function wrapped in a check that its first argument is a
MyClass instance.

The simplest workaround is probably to spell out the dictionary access

whatever = MyClass.__dict__["ClassDefaultTracer"]

If you want inheritance to work properly you need something more invoved,
perhaps (untested)

f = self.ClassDefaultTracer
try:
f = f.im_func
except AttributeError:
pass
self.InstanceTracer = f

or you wrap the callable in a descriptor:
.... def __init__(self, f):
.... self.f = f
.... def __get__(self, *args):
.... return self.f
........ old = DefaultTracer
.... new = D(DefaultTracer)
....<bound method MyClass.DefaultTracer of <__main__.MyClass object at
0x7f0f1cda6fd0>><function DefaultTracer at 0x7f0f1c0c45f0>

Not pretty, but in Python 3 the problem is gone...
 
S

Steven D'Aprano

Kannan said:
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:
.... print s
........ func = function
.... print func, type(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:
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:
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:
<__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.
 
P

Paul Rudin

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.

C-x r d
 
P

Peter Otten

Peter said:
or you wrap the callable in a descriptor:

... def __init__(self, f):
... self.f = f
... def __get__(self, *args):
... return self.f
...

After skimming over Steven's post: use staticmethod.
No idea why I didn't think of that myself.
 
Ad

Advertisements

K

Kannan Varadhan

Thanks folks,

Tried all of these, and went with staticmethod(). Was cleanest
solution,
After skimming over Steven's post: use staticmethod.
No idea why I didn't think of that myself.


Kannan
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top