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

Discussion in 'Python' started by Kannan Varadhan, Jul 7, 2011.

  1. 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
     
    Kannan Varadhan, Jul 7, 2011
    #1
    1. Advertisements

  2. Kannan Varadhan

    Peter Otten Guest

    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...
     
    Peter Otten, Jul 7, 2011
    #2
    1. Advertisements

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

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


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


    ought to do it.
     
    Steven D'Aprano, Jul 7, 2011
    #3
  4. Kannan Varadhan

    Paul Rudin Guest

    C-x r d
     
    Paul Rudin, Jul 7, 2011
    #4
  5. Kannan Varadhan

    Peter Otten Guest

    After skimming over Steven's post: use staticmethod.
    No idea why I didn't think of that myself.
     
    Peter Otten, Jul 7, 2011
    #5
  6. Thanks folks,

    Tried all of these, and went with staticmethod(). Was cleanest
    solution,

    Kannan
     
    Kannan Varadhan, Jul 11, 2011
    #6
    1. 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 (here). After that, you can post your question and our members will help you out.