Mannix
Mannix

Reputation: 431

TypeError: 'classname' object is not callable

I am working on a class to work with "x,y" data sets. The data typically comes from txt files, where the first column of data is stored in "x" and the second columns is stored in "y".

I am adding some "curve fitting" functionality to the class. And I am getting the error in the title of this post.

Here is the class:

class XY(object):
    import matplotlib.pyplot as plt
    from scipy.optimize import curve_fit
    import numpy as np
    def __init__(self,f=None):
        self.file(f)
        self.read()
        return
    def file(self,f=None):
        self.filename=self.get_filename(f)
        return
    def filename(self):
        return self.filename
    def get_filename(self,f):
        if(type(f)==str):
            filename=f
        elif(type(f)==file):
            filename=f.name
        else:
            filename=None
        return filename
    def read(self,f=None):
        if(f is None):
            if(self.filename is None):
                return
            else:   # Use internal filename
                filename=self.filename
        else:   # Change/set internal filename
            self.filename=self.get_filename(f)
            filename=self.filename
        data=[]
        try:
            with open(filename,'r') as F:
                for line in F:
                    data.append(line.split())
        except IOError as e:
            print("%s"%e)
            return
        F.close()
        for r in range(0,len(data)):
            for c in range(0,len(data[r])):
                data[r][c]=float(data[r][c])
        self.data=data
        self.x=[self.data[i][0] for i in range(0,len(self.data))]
        self.y=[self.data[i][1] for i in range(0,len(self.data))]
        return self.data
    def f00(self,x,a,b,c,d):
        return a*x**b*self.np.exp(-c*x)+d
    def cf00(self):
        popt,pcov=self.curve_fit(self.f00,self.x,self.y)
        self.y=self.f00(self.x,*popt)
        return self.y

I paste the class into python in interactive mode. And then try the following:

$ python
Python 2.7.14 (default, Oct 31 2017, 21:12:13)
[GCC 6.4.0] on cygwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

I paste in the class shown above and then try the following:

>>> xy=XY("rot-03-05.dat")
>>> len(xy.x)
220
>>> len(xy.y)
220
>>> xy.cf00()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 56, in cf00
  File "/usr/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 751, in curve_fit
    res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
  File "/usr/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 383, in leastsq
    shape, dtype = _check_func('leastsq', 'func', func, x0, args, n)
  File "/usr/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 27, in _check_func
    res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))
  File "/usr/lib/python2.7/site-packages/scipy/optimize/minpack.py", line 463, in func_wrapped
    return func(xdata, *params) - ydata
TypeError: 'XY' object is not callable
>>> xy.cf00
<bound method XY.cf00 of <__main__.XY object at 0x6ff5ea25fd0>>

I tried taking self out of f00() and cf00(). Didn't work.

I tried calling f00() from my instance, and it works:

>>> xy=XY()
>>> xy.f00(1,1,1,1,1)
1.3678794411714423

I used this curve fitting function elsewhere, and it works. Now, I am trying to implement it in a class.

Here are standalone functions that work when they're not part of a class:

def f00(x,a,b,c,d): return a*x**b*np.exp(-c*x)+d
def cf00(x,y):
    popt,pcov=curve_fit(f00,x,y,maxfev=1200000)
    return f00(x,*popt)

y1=cf00(x,y)

No problems.

Upvotes: 0

Views: 680

Answers (2)

Juan Diego Garcia
Juan Diego Garcia

Reputation: 823

So you are importing from scipy.optimize import curve_fit inside the class definition and python is binding that name to the class namespace, therefore when you call self.curve_fit it works, but keep in mind that curve_fit function is defined inside scipy.

When you call self.method() what is really happening is: type(self).method(self), so when you call self.curve_fit it is passing the class XY as the first parameter and it expects a function.

To solve that i recommend to put the imports at the very top of the file and just call curve_fit without the self

Upvotes: 2

C.Nivs
C.Nivs

Reputation: 13106

This is a large code block, but I've outlined what all should be fixed in your code including comments:

import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import numpy as np
from io import TextIOWrapper # to check file-handle type
# Import modules at the global level, makes life easier

class XY(object):

    def __init__(self,f=None):
        self.filename = self.get_filename(f)
        self.data, self.x, self.y = self.read()
        # Don't return from __init__
        # return

    # This function is unnecessary
    # def file(self,f=None):
    #     self.filename=self.get_filename(f)
    #     return

    # This doesn't do anything, just access the attribute like instance.filename
    # def filename(self):
    #     return self.filename

    def get_filename(self,f):
        if isinstance(f, str): # use isinstance for type checking
            filename=f
        elif isinstance(f, TextIOWrapper):
            filename=f.name
        else:
            filename=None # I'd probably raise an exception here
        return filename


    def read(self): # the f param wasn't used otherwise
        # The whole block here was just a repeat of self.get_filename, no
        # need for it
        data=[]
        try:
            with open(filename,'r') as F:
                for line in F:
                    data.append(line.split())
        except IOError as e:
            # I would just let the exception happen and raise it
            print("%s"%e)
            raise
            # return # Don't just return None, you'll get weird behavior
        # F is already closed, that's what with does
        # F.close()
        for r in range(0,len(data)):
            for c in range(0,len(data[r])):
                data[r][c]=float(data[r][c])

        x = [self.data[i][0] for i in range(0,len(self.data))]
        y = [self.data[i][1] for i in range(0,len(self.data))]
        return data, x, y # It's generally bad form to assign self.attr outside of __init__

    def f00(self,x,a,b,c,d):
        return a*x**b*np.exp(-c*x)+d

    def cf00(self):
        popt, pcov = curve_fit(self.f00, self.x, self.y) # you don't need self.curve fit since it's now globally imported
        self.y=self.f00(self.x, *popt)
        return self.y # Not sure you need to return this, since you can just access it after the function is called

For future reference, it's best to include only what is needed to replicate the problem you are facing, nothing more. It makes it much easier to isolate the problem. A minimal working example might look like the following:

import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import numpy as np

def XY(object):
    def __init__(self):
        self.x = [float(a) for a in range(100)]
        self.y = [float(a) for a in range(100)]
        self.data = list(zip(self.x, self.y))

    def f00(self,x,a,b,c,d):
        return a*x**b*np.exp(-c*x)+d

    def cf00(self):
        popt, pcov = curve_fit(self.f00, self.x, self.y)
        return self.f00(self.x, *popt)

Much easier to identify what's going wrong

Upvotes: 0

Related Questions