5

I am having an issue passing an instance variable of an object to an instance method.

I have searched for this elsewhere, but all I keep finding is information on how the object is passed to the method using self, which I already know, or just tutorials on general differences between class and instance methods that don't specifically answer my question. The answer to my question definitely exists somewhere, I think I just don't know what to actually ask for.

In my code, I have this class:

class SongData:

    def __init__(self, datapoint):
        self.artist = datapoint['artist']
        self.track = datapoint['name']

    def xtradata_rm(self, regex, string=None):
        if string is None:
            string = self
        srchrslts = re.search(regex, string)
        if srchrslts is not None:
            if regex == 'f.*?t':
                self = self.replace(string,'')
                self.xtradata_rm('\((.*?)\)')
            else:
                self.xtradata_rm('f.*?t', srchrslts)

    def example_method(self):
        #This one isn't actually in the code, included for ease of explanation.
        print(self) 

    #some more methods irrelevant to question down here.

Imagine we instantiate an object by doing song = SongData(datapoint). The method xtradata_rm is supposed to search either the song.artist or song.track string for a section in brackets, then if the section found contains any form of the word "featuring" remove that from the string and then try again until no more bracketed expressions with brackets containing "featuring" are found.

I am aware now this is probably 100% the wrong usage of self, but I don't know what to put in its place to achieve the behaviour I want. So then in my script I try to do:

file_list = glob.glob("*procData.json")


for datafname in file_list:
    datafile = json.load(open(datafname))

    for i, datapoint in enumerate(datafile['EnvDict']):
        song = SongData(datapoint)
        song.track.xtradata_rm('\((.*?)\)')
        song.releasefetch(lfmapi)
        song.dcsearcher(dcapi)
        datapoint.update({"album": song.release, "year": song.year})

    with open("upd" + datafname, 'w') as output:
        json.dump(datafile, output)

but then I get this error:

Traceback (most recent call last):
    song.track.xtradata_rm('\((.*?)\)')
AttributeError: 'str' object has no attribute 'xtradata_rm'

If I comment out that line, the code runs.

So my first question is, in general, what do I have to do so I can go song.track.example_method() or song.artist.example_method() and get track_name or artist_name printed in the console as expected respectively.

My second question is, how can I do the same with xtradata_rm (i.e. be able to do song.track.xtradata_rm('\((.*?)\)') and essentially insert song.track in place of self within the method), and how does xtradata_rm being recursive and trying to pass the instance variable implicitly to itself within itself change things?

Rhyanon
  • 53
  • 4
  • The issue with your code is that you are trying to pass `song.track` as `self` to the `xtradata_rm` method. Instead, use self.track inside your `xtradata_rm` method when you invoke it as `song.xtradata_rm('\((.*?)\)')` – JRajan Jun 15 '18 at 12:13

1 Answers1

4

Looks like you want to add method xtradata_rm to str objects self.artist and self.track.

One thing that you misunderstand about Python is that can't change your object by assigning something to the variable self (or any other variable). self = 123 doesn't change the object, that is behind the name self to 123, it makes the name self point to object 123 (and do it only inside current scope).

To really get this distinction you should watch the talk Facts and Myths about Python names and values by Ned Batchelder.

The other thing is that str objects are immutable, so even if names worked as you expected, you simply could not modify str. For example, bytearray is mutable, and str is not, see the difference:

In [1]: b = bytearray(b'My example string')

In [2]: id(b)
Out[2]: 4584776792

In [3]: b[3:10] = b'modified'

In [4]: b
Out[4]: bytearray(b'My modified string')

In [5]: id(b) # same object
Out[5]: 4584776792

In [6]: s = 'My example string'

In [7]: s[3:10] = 'modified'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-7-22fe89ae82a3> in <module>()
----> 1 s[3:10] = 'modified'

TypeError: 'str' object does not support item assignment

In [8]: new_s = s.replace('example', 'modified')

In [9]: id(new_s) # different object
Out[9]: 4584725936

In [10]: id(s)
Out[10]: 4584762296

In [11]: s # original string unmodified
Out[11]: 'My example string'

So to implement your method we need to create wrapper for str object that looks like str and acts like str, but also implements your method. This can be rather hard, for many complicated reasons proxying objects in python is a really involved ordeal.

But fear not! In the dephs of standard library lives a class (144 lines of boring code) just for you: collections.UserString.

All we need to do is to subclass it and implement your method on it:

class SongAttribute(collections.UserString):
    def example_mutate(self):
        """Works UNLIKE other string methods, mutates SongAttribute object,
        but I think this is how you want your code to work. Jugging just a bit ;) 
        Note: actual str object still is immutable and wasn't mutated,
        self.data now just references another immutable str object.

        P.S.: self.data is the object being proxied by UserString class
        """

        self.data = self.data.replace(' ', '_')
        return self

    def example_return_new(self):
        """Works like all other string metods, returns new string"""
        return self.replace(' ', '_')

song = SongAttribute('My Song Name') # creating new song attribute (artist or track)
print(song, type(song)) # it looks like str, but isn't
print(song.upper(), type(song.upper())) # it has all of the str methods, but they return SongAttribute objects, not str objects.

# Return new
print()
new_song = song.example_return_new()
print(new_song, type(new_song)) # we got underscored SongAttribute

# Mutate
print()
print(song, type(song))
print(song.example_mutate(), type(song.example_mutate())) # this method changed song object internally
print(song, type(song)) # and now we still see the changes

Output:

My Song Name <class '__main__.SongAttribute'>
MY SONG NAME <class '__main__.SongAttribute'>

My_Song_Name <class '__main__.SongAttribute'>

My Song Name <class '__main__.SongAttribute'>
My_Song_Name <class '__main__.SongAttribute'>
My_Song_Name <class '__main__.SongAttribute'>

Now you can implement your method on SongAttribute, and change SongData constructor to:

def __init__(self, datapoint):
    self.artist = SongAttribute(datapoint['artist'])
    self.track = SongAttribute(datapoint['name'])
Andrew Morozko
  • 2,576
  • 16
  • 16
  • **See also:** **Python 2x 3x compatibility** http://python-future.org/compatible_idioms.html#userdict-userlist-userstring – dreftymac Jul 01 '18 at 01:19