python object AttributeError: type object 'Track' has no attribute 'title'
Question: I apologize if this is a noob question, but I can't seem to figure this one
out.

I have defined an object that defines a music track (NOTE: originally had the
just ATTRIBUTE vs self.ATTRIBUTE. I edited those values in to help remove
confusion. They had no affect on the problem)

    
    
    class Track(object):
      def __init__(self, title, artist, album, source, dest):
        """
        Model of the Track Object
    
        Contains the followign attributes:
        'Title', 'Artist', 'Album', 'Source', 'Dest'
        """
        self.atrTitle = title
        self.atrArtist = artist
        self.atrAlbum = album
        self.atrSource = source
        self.atrDest = dest
    

I use ObjectListView to create a list of tracks in a specific directory

    
    
    ....other code....
    self.aTrack = [Track(sTitle,sArtist,sAlbum,sSource, sDestDir)]
    self.TrackOlv.AddObjects(self.aTrack)
    ....other code....
    

Now I want to iterate the list and print out a single value of each item

    
    
    list = self.TrackOlv.GetObjects()
    
    for item in list:
        print item.atrTitle
    

This fails with the error

    
    
    AttributeError: type object 'Track' has no attribute 'atrTitle'
    

What really confuses me is if I highlight a single item in the Object List
View display and use the following code, it will correctly print out the
single value for the highlighted item

    
    
    list = self.TrackOlv.GetSelectedObject()
    print list.atrTitle
    

EDIT: Full source per request. To see error, browse to source dir w/ .mp3
files then click the print button.

    
    
    #Boa:Frame:Frame1
    
    import wx
    import os
    import glob
    import shutil
    import datetime
    from mutagen.mp3 import MP3
    from mutagen.easyid3 import EasyID3
    import mutagen.id3
    import unicodedata
    
    from ObjectListView import ObjectListView, ColumnDefn
    
    
    
    ########################################################################
    class Track(object):
        def __init__(self, title, artist, album, source, dest):
            """
            Model of the Track Object
    
            Contains the followign attributes:
            'Title', 'Artist', 'Album', 'Source', 'Dest'
            """
            self.atrTitle = title
            self.atrArtist = artist
            self.atrAlbum = album
            self.atrSource = source
            self.atrDest = dest       
    
    class Action(object):
        def __init__(self, timestamp, action, result):
            self.timestamp = timestamp
            self.action = action
            self.result = result
    
    ########################################################################
    # Non GUI
    ########################################################################
    
    def selectFolder(sMessage):
        print "Select Folder"
        dlg = wx.DirDialog(None, message = sMessage)
    
        if dlg.ShowModal() == wx.ID_OK:
            # User has selected something, get the path, set the window's title to the path
            filename = dlg.GetPath()   
        else:
            filename = "None Selected"
    
        dlg.Destroy()
        return filename 
    
    def getList(SourceDir):
        print "getList"
        listOfFiles = None
        print "-list set to none"
    
        listOfFiles = glob.glob(SourceDir + '/*.mp3')
    
        return listOfFiles
    
    def getListRecursive(SourceDir):
        print "getListRecursive"
        listOfFiles = None
        listOfFiles = []
        print "-list set to none"
    
        for root, dirs, files in os.walk(SourceDir):
            for file in files:
                if file.endswith(".mp3"):
                    listOfFiles.append(os.path.join(root,file))
    
        #print listOfFiles
    
        return listOfFiles
    
    def strip_accents(s):
        print "strip_accents"
        return ''.join((c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn'))   
    
    def replace_all(text):
        print "replace_all " + text
        dictionary = {'\\':"", '?':"", '/':"", '...':"", ':':"", '&':"and"}
    
        print text
        print text.decode('utf-8')
    
        text = strip_accents(text.decode('utf-8'))
    
        for i, j in dictionary.iteritems():
            text = text.replace(i,j)
    
        return text
    
    def getTitle(fileName):
        print "getTitle"
        audio = MP3(fileName)
    
        try:
            sTitle = str(audio["TIT2"])
        except KeyError:
            sTitle = os.path.basename(fileName)
            frame.lvActions.Append([datetime.datetime.now(),fileName,"Title tag does not exist, set to filename"])
    
        # TODO: Offer to set title to filename
        ## If fileName != filename then
        ##  prompt user for action
        ##  Offer Y/n/a
    
        sTitle = replace_all(sTitle)
    
        return sTitle
    
    def getArtist(fileName):
        print "get artist"
    
        audio = MP3(fileName)
    
        try:
            sArtist = str(audio["TPE1"])
        except KeyError:
            sArtist = "unkown"
            frame.lvActions.Append([datetime.datetime.now(),fileName,"Artist tag does not exist, set to unkown"])
    
        #Replace all special chars that cause dir path errors
        sArtist = replace_all(sArtist)
    
        #if name = 'The Beatles' change to 'Beatles, The'
        if sArtist.lower().find('the') == 0:
            sArtist = sArtist.replace('the ',"")
            sArtist = sArtist.replace('The ',"")
            sArtist = sArtist + ", The"
    
        return sArtist
    
    def getAblum(fileName):
        print "get album"
        audio = MP3(fileName)
    
        try:
            sAlbum = str(audio["TALB"])
        except KeyError:
            sAlbum = "unkown"
            frame.lvActions.Append([datetime.datetime.now(),fileName,"Album tag does not exist, set to unkown"])
    
        #Replace all special chars that cause dir path error    
        sAlbum = replace_all(sAlbum)
        return sAlbum
    
    
    ########################################################################
    # GUI
    ########################################################################
    class MainPanel(wx.Panel):
        #----------------------------------------------------------------------
        def __init__(self, parent):
            wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
    
            self.TrackOlv = ObjectListView(self, wx.ID_ANY,
                                           style=wx.LC_REPORT|wx.SUNKEN_BORDER)
            self.setTracks() 
    
            # Allow the cell values to be edited when double-clicked
            self.TrackOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
    
            self.ActionsOlv = ObjectListView(self, wx.ID_ANY,
                                             style=wx.LC_REPORT|wx.SUNKEN_BORDER)
            self.setActions()         
    
            # create browse to source button
            sourceBtn = wx.Button(self, wx.ID_ANY, "Browse Source")
            sourceBtn.Bind(wx.EVT_BUTTON, self.onBrowseSource)        
    
            # create source txt box
            self.txSource = wx.TextCtrl(self, wx.ID_ANY, name=u'txSource', value=u'')
    
            # create browse dest button
            destBtn = wx.Button(self, wx.ID_ANY, "Browse Destination")
            destBtn.Bind(wx.EVT_BUTTON, self.onBrowseDest)
    
            # create dest txt box 
            self.txDest = wx.TextCtrl(self, wx.ID_ANY, name=u'txDest', value=u'')         
    
            # create Move Files button
            moveBtn = wx.Button(self, wx.ID_ANY, "Move Files")
            moveBtn.Bind(wx.EVT_BUTTON, self.onMoveFiles)
    
            # print list button - debug only
            printBtn = wx.Button(self, wx.ID_ANY, "Print List")
            printBtn.Bind(wx.EVT_BUTTON, self.onPrintBtn)
    
            # create check box to include all sub files
            self.cbSubfolders = wx.CheckBox(self, wx.ID_ANY,
                  label=u'Include Subfolders', name=u'cbSubfolders', style=0)
            self.cbSubfolders.SetValue(True)
            self.cbSubfolders.Bind(wx.EVT_CHECKBOX, self.OnCbSubfoldersCheckbox)
    
            # create check box to repace file names
            self.cbReplaceFilename = wx.CheckBox(self, wx.ID_ANY,
                  label=u'Replace Filename with Title Tag',
                  name=u'cbReplaceFilename', style=0)
            self.cbReplaceFilename.SetValue(False)
            self.cbReplaceFilename.Bind(wx.EVT_CHECKBOX, self.OnCbReplaceFilenameCheckbox)
    
            # Create some sizers
            mainSizer = wx.BoxSizer(wx.VERTICAL)
            feedbackSizer = wx.BoxSizer(wx.VERTICAL)
            sourceSizer = wx.BoxSizer(wx.HORIZONTAL)
            btnSizer = wx.BoxSizer(wx.HORIZONTAL) 
    
            feedbackSizer.Add(self.TrackOlv, 1, wx.ALL|wx.EXPAND, 2)
            feedbackSizer.Add(self.ActionsOlv, 1, wx.ALL|wx.EXPAND, 2)
    
            sourceSizer.Add(sourceBtn, 0, wx.ALL, 2)
            sourceSizer.Add(self.txSource, 1, wx.ALL|wx.EXPAND, 2)
    
            sourceSizer.Add(destBtn, 0, wx.ALL, 2)
            sourceSizer.Add(self.txDest, 1, wx.ALL|wx.EXPAND, 2)
    
            btnSizer.Add(printBtn)
            btnSizer.Add(moveBtn, 0, wx.ALL, 2)
            btnSizer.Add(self.cbSubfolders, 0, wx.ALL, 2)
            btnSizer.Add(self.cbReplaceFilename, 0, wx.ALL, 2)
    
            mainSizer.Add(feedbackSizer, 1 , wx.ALL|wx.EXPAND, 2)
            mainSizer.Add(sourceSizer, 0, wx.ALL|wx.EXPAND, 2)
            #mainSizer.Add(destSizer, 0, wx.ALL|wx.EXPAND, 2)
            #mainSizer.Add(destSizer, 0, wx.All|wx.Expand, 2)
            mainSizer.Add(btnSizer, 0, wx.ALL, 2)
            self.SetSizer(mainSizer)
            mainSizer.Fit(self)
    
        #----------------------------------------------------------------------
        # Set the GUI column headers and width
        #----------------------------------------------------------------------
        def setTracks(self, data=None):
            self.TrackOlv.SetColumns([
                ColumnDefn("Title", "left", 100, "title"),
                ColumnDefn("Artist", "left", 100, "artist"),
                ColumnDefn("Album", "left", 100, "album"),
                ColumnDefn("Source", "left", 300, "source"),
                ColumnDefn("Destination", "left", 300, "dest"),
            ]) 
    
        def setActions(self, data=None):
            self.ActionsOlv.SetColumns([
                ColumnDefn("Time", "left", 100, "timestamp"),
                ColumnDefn("Action", "left", 450, "action"),
                ColumnDefn("Result", "left", 450, "result")
            ])
    
        #----------------------------------------------------------------------
        # GUI EVENTS
        #-----------------------------------------------------------------------
        EventList = [Action]
    
        #Select Source of files
        def onBrowseSource(self, event):
            print "OnBrowseSource"    
            source = selectFolder("Select the Source Directory")
    
            print source
    
            self.txSource.SetValue(source)
            self.anEvent = [Action(datetime.datetime.now(),source,"Set as Source dir")]
            self.ActionsOlv.AddObjects(self.anEvent)
    
            self.populateList()
    
        #Select Source of files
        def onBrowseDest(self, event):
            print "OnBrowseDest"    
            dest = selectFolder("Select the Destination Directory")
    
            print dest
    
            self.txDest.SetValue(dest)
            self.anEvent = [Action(datetime.datetime.now(),dest,"Set as Destination dir")]
            self.ActionsOlv.AddObjects(self.anEvent)
    
            self.populateList()
    
        def OnCbSubfoldersCheckbox(self, event):
            print "cbSubfolder"
            self.populateList()       
    
        def OnCbReplaceFilenameCheckbox(self, event):
            print "cbReplaceFilename"
            self.populateList()
    
        def onMoveFiles(self, event):
            print "onMoveFiles"
            self.moveFiles()
    
        def onPrintBtn(self, event):
            print "onPrintBtn"
    
            #Why does this work
            #rowObj = self.dataOlv.GetSelectedObject()
            #print rowObj.author
            #print rowObj.title
    
            #debug - how many item in the list... why does it only print 1?
            test = self.TrackOlv.GetItemCount()
            print test
    
            print "aphex"
            print self.TrackOlv.GetObjects()
    
            for item in xrange(self.TrackOlv.GetItemCount()):
                stitle = self.TrackOlv.GetObjectAt(item)
    
                print stitle.atrTitle
    
    
        #-------------
        #Computations
        #-------------
    
        def defineDestFilename(self, sFullDestPath):
            print "define dest"
    
            iCopyX = 0
            bExists = False
            sOrigName = sFullDestPath
    
            #If the file does not exist return original path/filename
            if os.path.isfile(sFullDestPath) == False:
                print "-" + sFullDestPath + " is valid"
                return sFullDestPath
    
            #Add .copyX.mp3 to the end of the file and retest until a new filename is found
            while bExists == False:
                sFullDestPath = sOrigName
                iCopyX += 1
                sFullDestPath = sFullDestPath + ".copy" + str(iCopyX) + ".mp3"
                if os.path.isfile(sFullDestPath) == False:
                    print "-" + sFullDestPath + " is valid"
                    self.lvActions.Append([datetime.datetime.now(),"Desitnation filename changed since file exists",sFullDestPath])
                    bExists = True
    
            #return path/filename.copyX.mp3
            return sFullDestPath
    
    
        def populateList(self):
            print "populateList"
    
            sSource = self.txSource.Value
            sDest = self.txDest.Value
    
            #Initalize list to reset all values on any option change
            self.initialList = [Track]
            self.TrackOlv.SetObjects(self.initialList)
    
            #Create list of files
            if self.cbSubfolders.Value == True:
                listOfFiles = getListRecursive(sSource)
            else:
                listOfFiles = getList(sSource)    
    
            print listOfFiles
    
            #prompt if no files detected
            if listOfFiles == []:
                self.anEvent = [Action(datetime.datetime.now(),"Parse Source for .MP3 files","No .MP3 files in source directory")]
                self.ActionsOlv.AddObjects(self.anEvent)
    
            #Populate list after both Source and Dest are chosen
            if len(sDest) > 1 and len(sDest) > 1:     
                print "-iterate listOfFiles"
    
                for file in listOfFiles:
    
                    (sSource,sFilename) = os.path.split(file)
    
                    print sSource
                    print sFilename
    
                    #sFilename = os.path.basename(file)
                    sTitle = getTitle(file)
                    try:
                        sArtist = getArtist(file)
                    except UnicodeDecodeError:
                        print "unicode"
                        sArtist = "unkown"
    
                    sAlbum = getAblum(file)
    
                    # Make path = sDest + Artist + Album
                    sDestDir = os.path.join (sDest, sArtist)
                    sDestDir = os.path.join (sDestDir, sAlbum) 
    
                    #If file exists change destination to *.copyX.mp3
                    if self.cbReplaceFilename.Value == True:
                        sDestDir = self.defineDestFilename(os.path.join(sDestDir,sTitle))
                    else:
                        sDestDir = self.defineDestFilename(os.path.join(sDestDir,sFilename))
    
                    # Populate listview with drive contents
    
                    #sSource = self.txSource.Value
                    sDest = self.txDest.Value
    
                    # TODO: Make source = exact source of track, not parent source
                    # TODO: Seperate dest and filename
                    self.aTrack = Track(sTitle,sArtist,sAlbum,sSource, sDestDir)
                    self.TrackOlv.AddObjects(self.aTrack)
                    self.Update()
    
    
                    #populate list to later use in move command
                    #self.validatedMove.append([file,sDestDir])
                    print "-item added to SourceDest list"
            else:
                print "-list not iterated"
    
        def moveFiles (self):
            print "move files"
    
            #for track in self.TrackOlv:
            #    print "-iterate SourceDest"
            #    #create dir
            #    (sDest,filename) = os.path.split(self.TrackOlv)
            #    print "-check dest"
            #    
            #    if not os.path.exists(sDest):
            #        print "-Created dest"
            #        os.makedirs(sDest)
            #        self.lvActions.Append([datetime.datetime.now(),sDest,"Created"])
            #        self.Update()
            #        self.lvActions.EnsureVisible(self.lvActions.GetItemCount() -1)
            #
            #    #Move File
            #    print "-move file"
            #    shutil.move(SourceDest[0],SourceDest[1])
            #    self.lvActions.Append([datetime.datetime.now(),filename,"Moved"])
            #    self.Update()
            #    self.lvActions.EnsureVisible(self.lvActions.GetItemCount() -1)
            #
            #self.lvActions.Append([datetime.datetime.now(),"Move Complete","Success"])
            #self.Update()
            #self.lvActions.EnsureVisible(self.lvActions.GetItemCount() -1)    
    
    
    
    ########################################################################
    class MainFrame(wx.Frame):
        #----------------------------------------------------------------------
        def __init__(self):
            wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
                              title="MP3 Manager", size=(1024,768)) #W by H
            panel = MainPanel(self)
    
    ########################################################################
    class GenApp(wx.App):
    
        #----------------------------------------------------------------------
        def __init__(self, redirect=False, filename=None):
            wx.App.__init__(self, redirect, filename)
    
        #----------------------------------------------------------------------
        def OnInit(self):
            # create frame here
            frame = MainFrame()
            frame.Show()
            return True
    
    #----------------------------------------------------------------------
    def main():
        """
        Run the demo
        """
        app = GenApp()
        app.MainLoop()
    
    if __name__ == "__main__":
        main()
    


Answer: You didn't include the bug:

    
    
    ....other code....
    self.aTrack = [Track(sTitle,sArtist,sAlbum,sSource, sDestDir)]
    self.TrackOlv.AddObjects(self.aTrack)
    ....other code....
    

It's somewhere in the first "....other code....", probably on the line where
you first create the self.TrackOlv.

Look at your error message:

    
    
    AttributeError: type object 'Track' has no attribute 'atrTitle'
    

It's saying the actual class ("type object 'Track'") is lacking the attribute.
That is, you've added the actual class Track to your list instead of an
instance of that class. It's probably the first item in the list, or your loop
would have printed some titles before throwing the error.

Note the different error messages below:

    
    
    >>> class Foo(object): pass
    ...
    >>> Foo.cat
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: type object 'Foo' has no attribute 'cat'
    >>> Foo().cat
    Traceback (most recent call last):
      File "<input>", line 1, in <module>
    AttributeError: 'Foo' object has no attribute 'cat'
    

