title: convert attributes of an object to a string in python
body: <p>I have a list of of objects defined by </p>
  
  <pre><code>class Track(object):
    def __init__(self, title, artist, album, source, dest):
      self.title = title
      self.artist = artist
      self.album = album
      self.source = source
      self.dest = dest
  </code></pre>
  
  <p>On a gui I have a button that I tied to an event.  The output is supposed to print the contents of the list to the command line (not the gui)</p>
  
  <pre><code>def onPrintBtn(self, event):
      print "onPrintBtn"
  
      for track in self.TrackOlv:
         print(track)
  </code></pre>
  
  <p>For some reason this ended in a infinite loop of
      (class '<strong>main</strong>.Track')</p>
  
  <p>I understand that my problem is that the attributes are as part of the object, but what I don't know how to do is to convert them to string values so I can print them out as needed.</p>
  
  <p>Per request here is the entire code for my program.</p>
  
  <pre><code>#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):
          self.title = title
          self.artist = artist
          self.album = album
          self.source = source
          self.dest = dest
  
      def __str__(self):
          return "'%s' by %s on teh ablum '%s'\nSource: %s\nDest: %s" % \
              (self.title, self.artist, self.album, self.source, self.dest)
  
      def __unicode__(self):
          return unicode(str(self))
  
  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 = {'\\':"", '?':"", '/':"", '...':"", ':':"", '&amp;':"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"
  
          for track in self.TrackOlv:
              print (track)
      #-------------
      #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) &gt; 1 and len(sDest) &gt; 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()
  </code></pre>