简体   繁体   中英

python object AttributeError: type object 'Track' has no attribute 'title'

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()

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'

Why are you instantiating your Track objects inside single-member lists? Change the following code and see if that helps.

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

Change to:

self.aTrack = Track(sTitle,sArtist,sAlbum,sSource, sDestDir)
self.TrackOlv.AddObjects(self.aTrack)

By the way, I ran your code in my interpreter and it works fine:

IronPython 2.7 Beta 1 (2.7.0.10) on .NET 4.0.30319.1
Type "help", "copyright", "credits" or "license" for more information.
>>> 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
...
>>> list = [Track('good','h','g','1','d:')]
>>> for item in list:
...     print item.atrTitle
...
good
>>>

Update @ccwhite: Per your 2nd comment:

>>> list = [Track('1','2','3','4','5'), Track('a','b','c','d','e')]
>>> for item in list:
...     print item.atrTitle
...
1
a
>>>

You haven' really created attributes in an object, but rather local variables. You need to do that in following way:

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

You need to attach the property to the self object passed into Track's init method. Like this:

class Track(object):
  def __init__(self, title, artist, album, source, dest):
    """
    Add the properties to the self (aka this) object.
    """
    self.atrTitle = title
    self.atrArtist = artist
    self.atrAlbum = album
    self.atrSource = source
    self.atrDest = dest

Instead of declaring atrTitle and other attributes as

atrTitle = title
atrArtist = artist
atrAlbum = album
atrSource = source
atrDest = dest

You should add self. before each assignment, like this:

self.atrTitle = title
self.atrArtist = artist
self.atrAlbum = album
self.atrSource = source
self.atrDest = dest

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM