質問

When creating a new class instance, I'm trying to call a method in a different class however can't get it to work. Here's what I have:

class DataProject(object):    
    def __init__(self, name=none,input_file=None,datamode=None,comments=None,readnow=True):
        ..............
        # here's where I'm trying to call the method in the other class
        if type(input_file) == str:
            self.input_file_format = self.input_file.split(".")[-1]
            if readnow:
                getattr(Analysis(),'read_'+self.input_file_format)(self,input_file)

class Analysis(object):
    def __init__(self):
        pass # nothing happens here atm

    def read_xlsx(self,parent,input_file):
        """Method to parse xlsx files and dump them into a DataFrame"""
        xl = pd.ExcelFile(input_file)
        for s in sheet_names:
            parent.data[s]=xl.parse(s)

I'm getting a NameError: global name 'read_xlsx' is not defined when I run this with afile.xlxs as input which made me think that I just discovered a massive hole in my Python knowledge (not that there aren't many but they tend to be hard to see, sort of like big forests...).

I would have thought that getattr(Analysis(), ... ) would access the global name space in which it would find the Analysis class and its methods. And in fact print(globals().keys()) shows that Analysis is part of this:

['plt', 'mlab', '__builtins__', '__file__', 'pylab', 'DataProject', 'matplotlib', '__package__', 'W32', 'Helpers', 'time', 'pd', 'pyplot', 'np', '__name__', 'dt', 'Analysis', '__doc__']

What am I missing here?

EDIT:

The full traceback is:

Traceback (most recent call last):
  File "C:\MPython\dataAnalysis\dataAnalysis.py", line 101, in <module>
    a=DataProject(input_file='C:\\MPython\\dataAnalysis\\EnergyAnalysis\\afile.xlxs',readnow=True)
  File "C:\MPython\dataAnalysis\dataAnalysis.py", line 73, in __init__
    getattr(Analysis(),'read_'+self.input_file_format)(self,input_file)
  File "C:\MPython\dataAnalysis\dataAnalysis.py", line 90, in read_xls
    read_xlsx(input_file)
NameError: global name 'read_xlsx' is not defined

My main call is:

if __name__=="__main__":
    a=DataProject(input_file='C:\\MPython\\dataAnalysis\\EnergyAnalysis\\afile.xlx',readnow=True)
役に立ちましたか?

解決

From the full traceback, it appears that your DataProject class is calling (successfully) the Analysys.read_xls method, which in turn is trying to call read_xlsx. However, it's calling it as a global function, not as a method.

Probably you just need to replace the code on line 90, turning read_xlsx(input_file) into self.read_xlsx(input_file), though you might need to pass an extra parameter for the parent DataProject instance too.

他のヒント

getattr() works as you describe it in both Python2.x and Python3.x. The bug must be somewhere else.

This modification of your code (none of the core logic is changed) works fine for instance:

class DataProject(object):    
    def __init__(self, name="myname",input_file="xlsx",datamode=None,comments=None,readnow=True):
        if type(input_file) == str:
            self.input_file_format = input_file.split(".")[-1]
            if readnow:
                getattr(Analysis(),'read_'+self.input_file_format)(self,input_file)

class Analysis(object):
    def __init__(self):
        pass # nothing happens here atm

    def read_xlsx(self,parent,input_file):
        """Method to parse xlsx files and dumpt them into a DataFrame"""
        print("hello")

a=DataProject()

Output is:

$ python3 testfn.py
hello

Why using getattr() in this way is usually a bad idea

The way you are using getattr forces a naming convention on your methods (read_someformat). The naming of your methods should not be a core part of your program's logic. - You should always be able to change a function's name at every call and definition of that function and leave behaviour of the program intact.

If a file format needs to be handled by a specific method this logic should be delegated to some unit (e.g a function) with responsibility for this. One way (there are others) of doing this is to have a function which takes the input and decides which function needs to handle it:

def read_file(self,file,format):
    if format == `xls`:
        self.read_xls(file)
    if format == `csv`:
        self.read_csv(file)

The above snippet does have its issues too (a better way to do it would be the chain of responsibility pattern for example) but it will be fine for small scripts and is much nicer.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top