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?
Class methods don't need a class instance. They can't access the instance ( self ) but they have access to the class itself via cls . Static methods don't have access to cls or self . They work like regular functions but belong to the class's namespace.
We can access the instance variable using the object and dot ( . ) operator. In Python, to work with an instance variable and method, we use the self keyword. We use the self keyword as the first parameter to a method.
Class Variables are variables that are shared by all instances of a class. So while instance variable can be unique for each instance like our name,email and pay; class variable should be the-same for each instance.
A class variable is a variable that defines a particular property or attribute for a class. An instance variable is a variable whose value is specified to the Instance and shared among different instances. 2. We can share these variables between class and its subclasses. We cannot share these variables between classes.
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'])
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With