Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #9012 > unrolled thread

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

Started byKannan Varadhan <kvaradhan3@gmail.com>
First post2011-07-07 01:08 -0700
Last post2011-07-07 13:38 +0100
Articles 6 — 4 participants

Back to article view | Back to comp.lang.python


Contents

  TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead) Kannan Varadhan <kvaradhan3@gmail.com> - 2011-07-07 01:08 -0700
    Re: TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead) Peter Otten <__peter__@web.de> - 2011-07-07 11:04 +0200
      Re: TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead) Peter Otten <__peter__@web.de> - 2011-07-07 14:50 +0200
        Re: TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead) Kannan Varadhan <kvaradhan3@gmail.com> - 2011-07-10 19:13 -0700
    Re: TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2011-07-07 22:36 +1000
      Re: TypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead) Paul Rudin <paul.nospam@rudin.co.uk> - 2011-07-07 13:38 +0100

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

FromKannan Varadhan <kvaradhan3@gmail.com>
Date2011-07-07 01:08 -0700
SubjectTypeError: unbound method DefaultTracer() must be called with MyClass instance as first argument (got str instance instead)
Message-ID<38cc43c2-7f4d-4b30-9260-cc439fc7000a@a10g2000vbz.googlegroups.com>
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

[toc] | [next] | [standalone]


#9015

FromPeter Otten <__peter__@web.de>
Date2011-07-07 11:04 +0200
Message-ID<mailman.735.1310029448.1164.python-list@python.org>
In reply to#9012
Kannan Varadhan wrote:

> 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 DefaultTracer(*args): print args
...
>>> class D(object):
...     def __init__(self, f):
...             self.f = f
...     def __get__(self, *args):
...             return self.f
...
>>> class MyClass(object):
...     old = DefaultTracer
...     new = D(DefaultTracer)
...
>>> MyClass.old
<unbound method MyClass.DefaultTracer>
>>> MyClass.new
<function DefaultTracer at 0x7f0f1c0c45f0>
>>> m = MyClass()
>>> m.old
<bound method MyClass.DefaultTracer of <__main__.MyClass object at 
0x7f0f1cda6fd0>>
>>> m.new
<function DefaultTracer at 0x7f0f1c0c45f0>

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

> 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.

[toc] | [prev] | [next] | [standalone]


#9026

FromPeter Otten <__peter__@web.de>
Date2011-07-07 14:50 +0200
Message-ID<iv4a23$f53$1@solani.org>
In reply to#9015
Peter Otten wrote:

> or you wrap the callable in a descriptor:
> 
>>>> def DefaultTracer(*args): print args
> ...
>>>> class D(object):
> ...     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.

[toc] | [prev] | [next] | [standalone]


#9216

FromKannan Varadhan <kvaradhan3@gmail.com>
Date2011-07-10 19:13 -0700
Message-ID<2be5417f-6b4c-4296-bcb0-e01961701a79@h38g2000pro.googlegroups.com>
In reply to#9026
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

[toc] | [prev] | [next] | [standalone]


#9024

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2011-07-07 22:36 +1000
Message-ID<4e15a853$0$29982$c3e8da3$5496439d@news.astraweb.com>
In reply to#9012
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

[toc] | [prev] | [next] | [standalone]


#9025

FromPaul Rudin <paul.nospam@rudin.co.uk>
Date2011-07-07 13:38 +0100
Message-ID<8762ne6zbn.fsf@no-fixed-abode.cable.virginmedia.net>
In reply to#9024
Steven D'Aprano <steve+comp.lang.python@pearwood.info> writes:


> 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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web